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

Upload jobs/process/TrainSDRescaleProcess.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. jobs/process/TrainSDRescaleProcess.py +276 -0
jobs/process/TrainSDRescaleProcess.py ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import glob
2
+ import os
3
+ from collections import OrderedDict
4
+ import random
5
+ from typing import Optional, List
6
+
7
+ from safetensors.torch import save_file, load_file
8
+ from tqdm import tqdm
9
+
10
+ from toolkit.layers import ReductionKernel
11
+ from toolkit.stable_diffusion_model import PromptEmbeds
12
+ from toolkit.train_tools import get_torch_dtype, apply_noise_offset
13
+ import gc
14
+ from toolkit import train_tools
15
+
16
+ import torch
17
+ from .BaseSDTrainProcess import BaseSDTrainProcess, StableDiffusion
18
+
19
+
20
+ def flush():
21
+ torch.cuda.empty_cache()
22
+ gc.collect()
23
+
24
+
25
+ class RescaleConfig:
26
+ def __init__(
27
+ self,
28
+ **kwargs
29
+ ):
30
+ self.from_resolution = kwargs.get('from_resolution', 512)
31
+ self.scale = kwargs.get('scale', 0.5)
32
+ self.latent_tensor_dir = kwargs.get('latent_tensor_dir', None)
33
+ self.num_latent_tensors = kwargs.get('num_latent_tensors', 1000)
34
+ self.to_resolution = kwargs.get('to_resolution', int(self.from_resolution * self.scale))
35
+ self.prompt_dropout = kwargs.get('prompt_dropout', 0.1)
36
+
37
+
38
+ class PromptEmbedsCache:
39
+ prompts: dict[str, PromptEmbeds] = {}
40
+
41
+ def __setitem__(self, __name: str, __value: PromptEmbeds) -> None:
42
+ self.prompts[__name] = __value
43
+
44
+ def __getitem__(self, __name: str) -> Optional[PromptEmbeds]:
45
+ if __name in self.prompts:
46
+ return self.prompts[__name]
47
+ else:
48
+ return None
49
+
50
+
51
+ class TrainSDRescaleProcess(BaseSDTrainProcess):
52
+ def __init__(self, process_id: int, job, config: OrderedDict):
53
+ # pass our custom pipeline to super so it sets it up
54
+ super().__init__(process_id, job, config)
55
+ self.step_num = 0
56
+ self.start_step = 0
57
+ self.device = self.get_conf('device', self.job.device)
58
+ self.device_torch = torch.device(self.device)
59
+ self.rescale_config = RescaleConfig(**self.get_conf('rescale', required=True))
60
+ self.reduce_size_fn = ReductionKernel(
61
+ in_channels=4,
62
+ kernel_size=int(self.rescale_config.from_resolution // self.rescale_config.to_resolution),
63
+ dtype=get_torch_dtype(self.train_config.dtype),
64
+ device=self.device_torch,
65
+ )
66
+
67
+ self.latent_paths: List[str] = []
68
+ self.empty_embedding: PromptEmbeds = None
69
+
70
+ def before_model_load(self):
71
+ pass
72
+
73
+ def get_latent_tensors(self):
74
+ dtype = get_torch_dtype(self.train_config.dtype)
75
+
76
+ num_to_generate = 0
77
+ # check if dir exists
78
+ if not os.path.exists(self.rescale_config.latent_tensor_dir):
79
+ os.makedirs(self.rescale_config.latent_tensor_dir)
80
+ num_to_generate = self.rescale_config.num_latent_tensors
81
+ else:
82
+ # find existing
83
+ current_tensor_list = glob.glob(os.path.join(self.rescale_config.latent_tensor_dir, "*.safetensors"))
84
+ num_to_generate = self.rescale_config.num_latent_tensors - len(current_tensor_list)
85
+ self.latent_paths = current_tensor_list
86
+
87
+ if num_to_generate > 0:
88
+ print(f"Generating {num_to_generate}/{self.rescale_config.num_latent_tensors} latent tensors")
89
+
90
+ # unload other model
91
+ self.sd.unet.to('cpu')
92
+
93
+ # load aux network
94
+ self.sd_parent = StableDiffusion(
95
+ self.device_torch,
96
+ model_config=self.model_config,
97
+ dtype=self.train_config.dtype,
98
+ )
99
+ self.sd_parent.load_model()
100
+ self.sd_parent.unet.to(self.device_torch, dtype=dtype)
101
+ # we dont need text encoder for this
102
+
103
+ del self.sd_parent.text_encoder
104
+ del self.sd_parent.tokenizer
105
+
106
+ self.sd_parent.unet.eval()
107
+ self.sd_parent.unet.requires_grad_(False)
108
+
109
+ # save current seed state for training
110
+ rng_state = torch.get_rng_state()
111
+ cuda_rng_state = torch.cuda.get_rng_state() if torch.cuda.is_available() else None
112
+
113
+ text_embeddings = train_tools.concat_prompt_embeddings(
114
+ self.empty_embedding, # unconditional (negative prompt)
115
+ self.empty_embedding, # conditional (positive prompt)
116
+ self.train_config.batch_size,
117
+ )
118
+ torch.set_default_device(self.device_torch)
119
+
120
+ for i in tqdm(range(num_to_generate)):
121
+ dtype = get_torch_dtype(self.train_config.dtype)
122
+ # get a random seed
123
+ seed = torch.randint(0, 2 ** 32, (1,)).item()
124
+ # zero pad seed string to max length
125
+ seed_string = str(seed).zfill(10)
126
+ # set seed
127
+ torch.manual_seed(seed)
128
+ if torch.cuda.is_available():
129
+ torch.cuda.manual_seed(seed)
130
+
131
+ # # ger a random number of steps
132
+ timesteps_to = self.train_config.max_denoising_steps
133
+
134
+ # set the scheduler to the number of steps
135
+ self.sd.noise_scheduler.set_timesteps(
136
+ timesteps_to, device=self.device_torch
137
+ )
138
+
139
+ noise = self.sd.get_latent_noise(
140
+ pixel_height=self.rescale_config.from_resolution,
141
+ pixel_width=self.rescale_config.from_resolution,
142
+ batch_size=self.train_config.batch_size,
143
+ noise_offset=self.train_config.noise_offset,
144
+ ).to(self.device_torch, dtype=dtype)
145
+
146
+ # get latents
147
+ latents = noise * self.sd.noise_scheduler.init_noise_sigma
148
+ latents = latents.to(self.device_torch, dtype=dtype)
149
+
150
+ # get random guidance scale from 1.0 to 10.0 (CFG)
151
+ guidance_scale = torch.rand(1).item() * 9.0 + 1.0
152
+
153
+ # do a timestep of 1
154
+ timestep = 1
155
+
156
+ noise_pred_target = self.sd_parent.predict_noise(
157
+ latents,
158
+ text_embeddings=text_embeddings,
159
+ timestep=timestep,
160
+ guidance_scale=guidance_scale
161
+ )
162
+
163
+ # build state dict
164
+ state_dict = OrderedDict()
165
+ state_dict['noise_pred_target'] = noise_pred_target.to('cpu', dtype=torch.float16)
166
+ state_dict['latents'] = latents.to('cpu', dtype=torch.float16)
167
+ state_dict['guidance_scale'] = torch.tensor(guidance_scale).to('cpu', dtype=torch.float16)
168
+ state_dict['timestep'] = torch.tensor(timestep).to('cpu', dtype=torch.float16)
169
+ state_dict['timesteps_to'] = torch.tensor(timesteps_to).to('cpu', dtype=torch.float16)
170
+ state_dict['seed'] = torch.tensor(seed).to('cpu', dtype=torch.float32) # must be float 32 to prevent overflow
171
+
172
+ file_name = f"{seed_string}_{i}.safetensors"
173
+ file_path = os.path.join(self.rescale_config.latent_tensor_dir, file_name)
174
+ save_file(state_dict, file_path)
175
+ self.latent_paths.append(file_path)
176
+
177
+ print("Removing parent model")
178
+ # delete parent
179
+ del self.sd_parent
180
+ flush()
181
+
182
+ torch.set_rng_state(rng_state)
183
+ if cuda_rng_state is not None:
184
+ torch.cuda.set_rng_state(cuda_rng_state)
185
+ self.sd.unet.to(self.device_torch, dtype=dtype)
186
+
187
+ def hook_before_train_loop(self):
188
+ # encode our empty prompt
189
+ self.empty_embedding = self.sd.encode_prompt("")
190
+ self.empty_embedding = self.empty_embedding.to(self.device_torch,
191
+ dtype=get_torch_dtype(self.train_config.dtype))
192
+
193
+ # Move train model encoder to cpu
194
+ if isinstance(self.sd.text_encoder, list):
195
+ for encoder in self.sd.text_encoder:
196
+ encoder.to('cpu')
197
+ encoder.eval()
198
+ encoder.requires_grad_(False)
199
+ else:
200
+ self.sd.text_encoder.to('cpu')
201
+ self.sd.text_encoder.eval()
202
+ self.sd.text_encoder.requires_grad_(False)
203
+
204
+ # self.sd.unet.to('cpu')
205
+ flush()
206
+
207
+ self.get_latent_tensors()
208
+
209
+ flush()
210
+ # end hook_before_train_loop
211
+
212
+ def hook_train_loop(self, batch):
213
+ dtype = get_torch_dtype(self.train_config.dtype)
214
+
215
+ loss_function = torch.nn.MSELoss()
216
+
217
+ # train it
218
+ # Begin gradient accumulation
219
+ self.sd.unet.train()
220
+ self.sd.unet.requires_grad_(True)
221
+ self.sd.unet.to(self.device_torch, dtype=dtype)
222
+
223
+ with torch.no_grad():
224
+ self.optimizer.zero_grad()
225
+
226
+ # pick random latent tensor
227
+ latent_path = random.choice(self.latent_paths)
228
+ latent_tensor = load_file(latent_path)
229
+
230
+ noise_pred_target = (latent_tensor['noise_pred_target']).to(self.device_torch, dtype=dtype)
231
+ latents = (latent_tensor['latents']).to(self.device_torch, dtype=dtype)
232
+ guidance_scale = (latent_tensor['guidance_scale']).item()
233
+ timestep = int((latent_tensor['timestep']).item())
234
+ timesteps_to = int((latent_tensor['timesteps_to']).item())
235
+ # seed = int((latent_tensor['seed']).item())
236
+
237
+ text_embeddings = train_tools.concat_prompt_embeddings(
238
+ self.empty_embedding, # unconditional (negative prompt)
239
+ self.empty_embedding, # conditional (positive prompt)
240
+ self.train_config.batch_size,
241
+ )
242
+ self.sd.noise_scheduler.set_timesteps(
243
+ timesteps_to, device=self.device_torch
244
+ )
245
+
246
+ denoised_target = self.sd.noise_scheduler.step(noise_pred_target, timestep, latents).prev_sample
247
+
248
+ # get the reduced latents
249
+ # reduced_pred = self.reduce_size_fn(noise_pred_target.detach())
250
+ denoised_target = self.reduce_size_fn(denoised_target.detach())
251
+ reduced_latents = self.reduce_size_fn(latents.detach())
252
+
253
+ denoised_target.requires_grad = False
254
+ self.optimizer.zero_grad()
255
+ noise_pred_train = self.sd.predict_noise(
256
+ reduced_latents,
257
+ text_embeddings=text_embeddings,
258
+ timestep=timestep,
259
+ guidance_scale=guidance_scale
260
+ )
261
+ denoised_pred = self.sd.noise_scheduler.step(noise_pred_train, timestep, reduced_latents).prev_sample
262
+ loss = loss_function(denoised_pred, denoised_target)
263
+ loss_float = loss.item()
264
+ loss.backward()
265
+ self.optimizer.step()
266
+ self.lr_scheduler.step()
267
+ self.optimizer.zero_grad()
268
+
269
+ flush()
270
+
271
+ loss_dict = OrderedDict(
272
+ {'loss': loss_float},
273
+ )
274
+
275
+ return loss_dict
276
+ # end hook_train_loop