recoilme commited on
Commit
9904f72
·
verified ·
1 Parent(s): 3d8f3a8

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. train-Copy1.py +896 -0
train-Copy1.py ADDED
@@ -0,0 +1,896 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #from comet_ml import Experiment
2
+ import os
3
+ import math
4
+ import torch
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+ from torch.utils.data import DataLoader, Sampler
8
+ from torch.utils.data.distributed import DistributedSampler
9
+ from torch.optim.lr_scheduler import LambdaLR
10
+ from collections import defaultdict
11
+ from diffusers import UNet2DConditionModel, AutoencoderKL,AutoencoderKLFlux2,AsymmetricAutoencoderKL
12
+ from accelerate import Accelerator
13
+ from datasets import load_from_disk
14
+ from tqdm import tqdm
15
+ from PIL import Image, ImageOps
16
+ import wandb
17
+ import random
18
+ import gc
19
+ from accelerate.state import DistributedType
20
+ from torch.distributed import broadcast_object_list
21
+ from torch.utils.checkpoint import checkpoint
22
+ from diffusers.models.attention_processor import AttnProcessor2_0
23
+ from datetime import datetime
24
+ import bitsandbytes as bnb
25
+ import torch.nn.functional as F
26
+ from collections import deque
27
+ from transformers import AutoTokenizer, AutoModel
28
+
29
+ # --------------------------- Параметры ---------------------------
30
+ ds_path = "/workspace/sd15flow/datasets/mjnj256"
31
+ project = "sdxs_sd15"
32
+ batch_size = 48
33
+ base_learning_rate = 4e-5 #2.7e-5
34
+ min_learning_rate = 2e-5 #2.7e-5
35
+ num_epochs = 50
36
+ sample_interval_share = 5
37
+ cfg_dropout = 0.15
38
+ max_length = 77 #192
39
+ use_wandb = True
40
+ use_comet_ml = False
41
+ save_model = True
42
+ use_decay = True
43
+ fbp = False
44
+ optimizer_type = "adam8bit"
45
+ torch_compile = False
46
+ unet_gradient = True
47
+ fixed_seed = False
48
+ shuffle = True
49
+ comet_ml_api_key = "Agctp26mbqnoYrrlvQuKSTk6r"
50
+ comet_ml_workspace = "recoilme"
51
+ torch.backends.cuda.matmul.allow_tf32 = True
52
+ torch.backends.cudnn.allow_tf32 = True
53
+ torch.backends.cuda.enable_mem_efficient_sdp(False)
54
+ dtype = torch.float32
55
+ save_barrier = 1.01
56
+ warmup_percent = 0.01
57
+ percentile_clipping = 95 #96 #97
58
+ betta2 = 0.995
59
+ eps = 1e-7
60
+ clip_grad_norm = 1.0
61
+ limit = 0
62
+ checkpoints_folder = ""
63
+ mixed_precision = "no"
64
+ gradient_accumulation_steps = 1
65
+
66
+ accelerator = Accelerator(
67
+ mixed_precision=mixed_precision,
68
+ gradient_accumulation_steps=gradient_accumulation_steps
69
+ )
70
+ device = accelerator.device
71
+
72
+ # Параметры для диффузии
73
+ n_diffusion_steps = 40
74
+ samples_to_generate = 12
75
+ guidance_scale = 4
76
+
77
+ # Папки для сохранения результатов
78
+ generated_folder = "samples"
79
+ os.makedirs(generated_folder, exist_ok=True)
80
+
81
+ # Настройка seed
82
+ current_date = datetime.now()
83
+ seed = int(current_date.strftime("%Y%m%d"))
84
+ if fixed_seed:
85
+ torch.manual_seed(seed)
86
+ np.random.seed(seed)
87
+ random.seed(seed)
88
+ if torch.cuda.is_available():
89
+ torch.cuda.manual_seed_all(seed)
90
+
91
+ # --------------------------- Параметры LoRA ---------------------------
92
+ lora_name = ""
93
+ lora_rank = 32
94
+ lora_alpha = 64
95
+
96
+ print("init")
97
+
98
+ loss_ratios = {
99
+ "mse": 1.25,
100
+ "mae": 0.25,
101
+ }
102
+ median_coeff_steps = 256
103
+
104
+ # Нормализация лоссов по медианам: считаем КОЭФФИЦИЕНТЫ
105
+ class MedianLossNormalizer:
106
+ def __init__(self, desired_ratios: dict, window_steps: int):
107
+ # нормируем доли на случай, если сумма != 1
108
+ #s = sum(desired_ratios.values())
109
+ #self.ratios = {k: (v / s) for k, v in desired_ratios.items()}
110
+ self.ratios = {k: float(v) for k, v in desired_ratios.items()}
111
+ self.buffers = {k: deque(maxlen=window_steps) for k in self.ratios.keys()}
112
+ self.window = window_steps
113
+
114
+ def update_and_total(self, losses: dict):
115
+ """
116
+ losses: dict ключ->тензор (значения лоссов)
117
+ Поведение:
118
+ - буферим ABS(l) только для активных (ratio>0) лоссов
119
+ - coeff = ratio / median(abs(loss))
120
+ - total = sum(coeff * loss) по активным лоссам
121
+ CHANGED: буферим abs() — чтобы медиана была положительной и не ломала деление.
122
+ """
123
+ # буферим только активные лоссы
124
+ for k, v in losses.items():
125
+ if k in self.buffers and self.ratios.get(k, 0) > 0:
126
+ val = v.detach().abs().mean().cpu().item() # .item() лучше float() для тензоров
127
+ self.buffers[k].append(val)
128
+ #self.buffers[k].append(float(v.detach().abs().cpu()))
129
+
130
+ meds = {k: (np.median(self.buffers[k]) if len(self.buffers[k]) > 0 else 1.0) for k in self.buffers}
131
+ coeffs = {k: (self.ratios[k] / max(meds[k], 1e-12)) for k in self.ratios}
132
+
133
+ # суммируем только по активным (ratio>0)
134
+ total = sum(coeffs[k] * losses[k] for k in coeffs if self.ratios.get(k, 0) > 0)
135
+ return total, coeffs, meds
136
+
137
+ # создаём normalizer после определения loss_ratios
138
+ normalizer = MedianLossNormalizer(loss_ratios, median_coeff_steps)
139
+
140
+ # --------------------------- Инициализация WandB ---------------------------
141
+ if accelerator.is_main_process:
142
+ if use_wandb:
143
+ wandb.init(project=project+lora_name, config={
144
+ "batch_size": batch_size,
145
+ "base_learning_rate": base_learning_rate,
146
+ "num_epochs": num_epochs,
147
+ "optimizer_type": optimizer_type,
148
+ })
149
+ if use_comet_ml:
150
+ from comet_ml import Experiment
151
+ comet_experiment = Experiment(
152
+ api_key=comet_ml_api_key,
153
+ project_name=project,
154
+ workspace=comet_ml_workspace
155
+ )
156
+ hyper_params = {
157
+ "batch_size": batch_size,
158
+ "base_learning_rate": base_learning_rate,
159
+ "num_epochs": num_epochs,
160
+ }
161
+ comet_experiment.log_parameters(hyper_params)
162
+
163
+ # Включение Flash Attention 2/SDPA
164
+ torch.backends.cuda.enable_flash_sdp(True)
165
+
166
+ # --------------------------- Загрузка моделей ---------------------------
167
+ #vae = AutoencoderKL.from_pretrained("vae", torch_dtype=dtype).to("cpu").eval()
168
+ #vae = AutoencoderKLFlux2.from_pretrained("black-forest-labs/FLUX.2-dev",subfolder="vae",torch_dtype=dtype).to(device).eval()
169
+ vae = AsymmetricAutoencoderKL.from_pretrained("/workspace/sd15flow/vae",torch_dtype=dtype).to(device).eval()
170
+ tokenizer = AutoTokenizer.from_pretrained("tokenizer")
171
+ text_model = AutoModel.from_pretrained("text_encoder").to(device).eval()
172
+
173
+ def encode_texts(texts, max_length=77): # Для SD 1.5 лучше жестко 77
174
+ if texts is None:
175
+ texts = [""]
176
+
177
+ if isinstance(texts, str):
178
+ texts = [texts]
179
+
180
+ with torch.no_grad():
181
+ # 1. Основная токенизация
182
+ toks = tokenizer(
183
+ texts,
184
+ padding="max_length",
185
+ max_length=max_length,
186
+ truncation=True,
187
+ return_tensors="pt"
188
+ ).to(device)
189
+
190
+ text_input_ids = toks.input_ids
191
+
192
+ # 2. Проверка на обрезку текста (логика для вывода предупреждений)
193
+ # Получаем айдишники без обрезки по длине
194
+ untruncated_ids = tokenizer(texts, padding="longest", return_tensors="pt").input_ids.to(device)
195
+
196
+ # Исправляем проверку: сравниваем тензоры, а не объект BatchEncoding
197
+ if untruncated_ids.shape[-1] > text_input_ids.shape[-1] and not torch.equal(
198
+ text_input_ids, untruncated_ids[:, :max_length]
199
+ ):
200
+ # Заменяем self.tokenizer на tokenizer
201
+ removed_text = tokenizer.batch_decode(
202
+ untruncated_ids[:, max_length - 1 : -1]
203
+ )
204
+ #print(f"Warning: Text truncated. Removed part: {removed_text}")
205
+
206
+ # 3. Маска внимания
207
+ #if hasattr(text_model.config, "use_attention_mask") and text_model.config.use_attention_mask:
208
+ # attention_mask = toks.attention_mask
209
+ #else:
210
+ #attention_mask = None
211
+ attention_mask = toks.attention_mask
212
+
213
+ # 4. Прогон через модель
214
+ # Правильный вызов: передаем конкретные тензоры или распаковываем словарь **toks
215
+ outputs = text_model(
216
+ input_ids=text_input_ids,
217
+ attention_mask=attention_mask,
218
+ output_hidden_states=True # Часто нужно для SD 1.5 (слой -2)
219
+ )
220
+
221
+ # prompt_embeds = outputs[0] — это last_hidden_state
222
+ # Если ты тренируешь Flow Matching по классике SD 1.5,
223
+ # возможно тебе нужен предпоследний слой:
224
+ prompt_embeds = outputs.hidden_states[0]
225
+
226
+ return prompt_embeds, attention_mask
227
+
228
+ # --- [UPDATED] Функция кодирования текста (с маской и пулингом) ---
229
+ def encode_texts22(texts, max_length=max_length):
230
+ # Если тексты пустые (для unconditional), создаем заглушки
231
+ if texts is None:
232
+ # В случае None возвращаем нули (логика для get_negative_embedding)
233
+ # Но здесь мы обычно ожидаем список строк.
234
+ pass
235
+
236
+ with torch.no_grad():
237
+ if isinstance(texts, str):
238
+ texts = [texts]
239
+
240
+ #for i, prompt_item in enumerate(texts):
241
+ # messages = [
242
+ # {"role": "user", "content": prompt_item},
243
+ # ]
244
+ # prompt_item = tokenizer.apply_chat_template(
245
+ # messages,
246
+ # tokenize=False,
247
+ # add_generation_prompt=True,
248
+ # enable_thinking=True,
249
+ # )
250
+ #print(prompt_item+"\n")
251
+ # texts[i] = prompt_item
252
+
253
+ toks = tokenizer(
254
+ texts,
255
+ return_tensors="pt",
256
+ padding="max_length",
257
+ truncation=True,
258
+ max_length=max_length
259
+ ).to(device)
260
+
261
+ text_input_ids = toks.input_ids
262
+ untruncated_ids = tokenizer(texts, padding="longest", return_tensors="pt").input_ids
263
+
264
+ if untruncated_ids.shape[-1] >= toks.shape[-1] and not torch.equal(
265
+ toks, untruncated_ids
266
+ ):
267
+ removed_text = tokenizer.batch_decode(
268
+ untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1]
269
+ )
270
+ print(
271
+ "The following part of your input was truncated because CLIP can only handle sequences up to"
272
+ )
273
+
274
+ attention_mask = toks.attention_mask.to(device)
275
+
276
+ prompt_embeds = text_model(toks.to(device), attention_mask=attention_mask)
277
+ prompt_embeds = prompt_embeds[-1]
278
+ return prompt_embeds, attention_mask
279
+ #outs = text_model(**toks, output_hidden_states=True, return_dict=True)
280
+
281
+ # Используем last_hidden_state или hidden_states[-1] (если Qwen, лучше last_hidden_state - прим человека: ХУЙ)
282
+ #hidden = outs.hidden_states[-2]
283
+
284
+ # 2. Маска внимания
285
+ #attention_mask = toks["attention_mask"]
286
+
287
+ # 3. Пулинг-эмбеддинг (Последний токен)
288
+ #sequence_lengths = attention_mask.sum(dim=1) - 1
289
+ #batch_size = hidden.shape[0]
290
+ #pooled = hidden[torch.arange(batch_size, device=hidden.device), sequence_lengths]
291
+
292
+ #return hidden, attention_mask
293
+ # --- НОВАЯ ЛОГИКА: ОБЪЕДИНЕНИЕ ДЛЯ КРОСС-ВНИМАНИЯ ---
294
+ # 1. Расширяем пулинг-вектор до последовательности [B, 1, emb]
295
+ #pooled_expanded = pooled.unsqueeze(1)
296
+
297
+ # 2. Объединяем последовательность токенов и пулинг-вектор
298
+ # !!! ИЗМЕНЕНИЕ ЗДЕСЬ !!!: Пулинг идет ПЕРВЫМ
299
+ # Теперь: [B, 1 + L, emb]. Пулинг стал токеном в НАЧАЛЕ.
300
+ #new_encoder_hidden_states = torch.cat([pooled_expanded, hidden], dim=1)
301
+
302
+ # 3. Обновляем маску внимания для нового токена
303
+ # Маска внимания: [B, 1 + L]. Добавляем 1 в НАЧАЛО.
304
+ # torch.ones((batch_size, 1), device=device) создает маску [B, 1] со значениями 1.
305
+ #new_attention_mask = torch.cat([torch.ones((batch_size, 1), device=device), attention_mask], dim=1)
306
+
307
+ #return new_encoder_hidden_states, new_attention_mask
308
+
309
+ shift_factor = getattr(vae.config, "shift_factor", 0.0)
310
+ if shift_factor is None: shift_factor = 0.0
311
+ scaling_factor = getattr(vae.config, "scaling_factor", 1.0)
312
+ if scaling_factor is None: scaling_factor = 1.0
313
+ scaling_factor = 1.0
314
+
315
+ from diffusers import FlowMatchEulerDiscreteScheduler
316
+ num_train_timesteps = 1000
317
+ def scale_model_input(self, sample, timestep):
318
+ return sample
319
+ FlowMatchEulerDiscreteScheduler.scale_model_input = scale_model_input
320
+
321
+ scheduler = FlowMatchEulerDiscreteScheduler(num_train_timesteps=num_train_timesteps)
322
+
323
+ class DistributedResolutionBatchSampler(Sampler):
324
+ def __init__(self, dataset, batch_size, num_replicas, rank, shuffle=True, drop_last=True):
325
+ self.dataset = dataset
326
+ self.batch_size = max(1, batch_size // num_replicas)
327
+ self.num_replicas = num_replicas
328
+ self.rank = rank
329
+ self.shuffle = shuffle
330
+ self.drop_last = drop_last
331
+ self.epoch = 0
332
+
333
+ try:
334
+ widths = np.array(dataset["width"])
335
+ heights = np.array(dataset["height"])
336
+ except KeyError:
337
+ widths = np.zeros(len(dataset))
338
+ heights = np.zeros(len(dataset))
339
+
340
+ self.size_keys = np.unique(np.stack([widths, heights], axis=1), axis=0)
341
+ self.size_groups = {}
342
+ for w, h in self.size_keys:
343
+ mask = (widths == w) & (heights == h)
344
+ self.size_groups[(w, h)] = np.where(mask)[0]
345
+
346
+ self.group_num_batches = {}
347
+ total_batches = 0
348
+ for size, indices in self.size_groups.items():
349
+ num_full_batches = len(indices) // (self.batch_size * self.num_replicas)
350
+ self.group_num_batches[size] = num_full_batches
351
+ total_batches += num_full_batches
352
+
353
+ self.num_batches = (total_batches // self.num_replicas) * self.num_replicas
354
+
355
+ def __iter__(self):
356
+ if torch.cuda.is_available():
357
+ torch.cuda.empty_cache()
358
+ all_batches = []
359
+ rng = np.random.RandomState(self.epoch)
360
+
361
+ for size, indices in self.size_groups.items():
362
+ indices = indices.copy()
363
+ if self.shuffle:
364
+ rng.shuffle(indices)
365
+ num_full_batches = self.group_num_batches[size]
366
+ if num_full_batches == 0:
367
+ continue
368
+ valid_indices = indices[:num_full_batches * self.batch_size * self.num_replicas]
369
+ batches = valid_indices.reshape(-1, self.batch_size * self.num_replicas)
370
+ start_idx = self.rank * self.batch_size
371
+ end_idx = start_idx + self.batch_size
372
+ gpu_batches = batches[:, start_idx:end_idx]
373
+ all_batches.extend(gpu_batches)
374
+
375
+ if self.shuffle:
376
+ rng.shuffle(all_batches)
377
+ accelerator.wait_for_everyone()
378
+ return iter(all_batches)
379
+
380
+ def __len__(self):
381
+ return self.num_batches
382
+
383
+ def set_epoch(self, epoch):
384
+ self.epoch = epoch
385
+
386
+ # --- [UPDATED] Функция для фиксированных семплов ---
387
+ def get_fixed_samples_by_resolution(dataset, samples_per_group=1):
388
+ size_groups = defaultdict(list)
389
+ try:
390
+ widths = dataset["width"]
391
+ heights = dataset["height"]
392
+ except KeyError:
393
+ widths = [0] * len(dataset)
394
+ heights = [0] * len(dataset)
395
+ for i, (w, h) in enumerate(zip(widths, heights)):
396
+ size = (w, h)
397
+ size_groups[size].append(i)
398
+
399
+ fixed_samples = {}
400
+ for size, indices in size_groups.items():
401
+ n_samples = min(samples_per_group, len(indices))
402
+ if len(size_groups)==1:
403
+ n_samples = samples_to_generate
404
+ if n_samples == 0:
405
+ continue
406
+ sample_indices = random.sample(indices, n_samples)
407
+ samples_data = [dataset[idx] for idx in sample_indices]
408
+
409
+ latents = torch.tensor(np.array([item["vae"] for item in samples_data])).to(device=device, dtype=dtype)
410
+ texts = [item["text"] for item in samples_data]
411
+
412
+ # Кодируем тексты на лету, чтобы получить маски и пулинг
413
+ embeddings, masks = encode_texts(texts)
414
+
415
+ fixed_samples[size] = (latents, embeddings, masks, texts)
416
+
417
+ print(f"Создано {len(fixed_samples)} групп фиксированных семплов по разрешениям")
418
+ return fixed_samples
419
+
420
+ if limit > 0:
421
+ dataset = load_from_disk(ds_path).select(range(limit))
422
+ else:
423
+ dataset = load_from_disk(ds_path)
424
+
425
+ dataset = dataset.filter(
426
+ lambda x: [not (path.startswith("/workspace/ds/animesfw") or path.startswith("/workspace/ds/d4/animesfw")) for path in x["image_path"]],
427
+ batched=True,
428
+ batch_size=10000, # обрабатываем по 10к строк за раз
429
+ num_proc=8
430
+ )
431
+ print(f"Осталось примеров после фильтрации: {len(dataset)}")
432
+
433
+ # --- [UPDATED] Collate Function ---
434
+ def collate_fn_simple(batch):
435
+ # 1. Латенты (VAE)
436
+ latents = torch.tensor(np.array([item["vae"] for item in batch])).to(device, dtype=dtype)
437
+
438
+ # 2. Текст берем сырой из датасета
439
+ raw_texts = [item["text"] for item in batch]
440
+ texts = [
441
+ "" if t.lower().startswith("zero")
442
+ else "" if random.random() < cfg_dropout
443
+ else t[1:].lstrip() if t.startswith(".")
444
+ else t.replace("The image shows ", "").replace("The image is ", "").replace("This image captures ","").strip()
445
+ for t in raw_texts
446
+ ]
447
+
448
+ # 3. Кодируем на лету
449
+ # Возвращает: hidden (B, L, D), mask (B, L)
450
+ embeddings, attention_mask = encode_texts(texts)
451
+
452
+ # attention_mask от токенизатора уже имеет нужный формат, но на всякий случай приведем к long
453
+ attention_mask = attention_mask.to(dtype=torch.int64)
454
+
455
+ return latents, embeddings, attention_mask
456
+
457
+ batch_sampler = DistributedResolutionBatchSampler(
458
+ dataset=dataset,
459
+ batch_size=batch_size,
460
+ num_replicas=accelerator.num_processes,
461
+ rank=accelerator.process_index,
462
+ shuffle=shuffle
463
+ )
464
+
465
+ dataloader = DataLoader(dataset, batch_sampler=batch_sampler, collate_fn=collate_fn_simple)
466
+ if accelerator.is_main_process:
467
+ print("Total samples", len(dataloader))
468
+ dataloader = accelerator.prepare(dataloader)
469
+
470
+ start_epoch = 0
471
+ global_step = 0
472
+ total_training_steps = (len(dataloader) * num_epochs)
473
+ world_size = accelerator.state.num_processes
474
+
475
+ # Загрузка UNet
476
+ latest_checkpoint = os.path.join(checkpoints_folder, project)
477
+ if os.path.isdir(latest_checkpoint):
478
+ print("Загружаем UNet из чекпоинта:", latest_checkpoint)
479
+ unet = UNet2DConditionModel.from_pretrained(latest_checkpoint).to(device=device, dtype=dtype)
480
+ if unet_gradient:
481
+ unet.enable_gradient_checkpointing()
482
+ unet.set_use_memory_efficient_attention_xformers(False)
483
+ try:
484
+ unet.set_attn_processor(AttnProcessor2_0())
485
+ except Exception as e:
486
+ print(f"Ошибка при включении SDPA: {e}")
487
+ unet.set_use_memory_efficient_attention_xformers(True)
488
+ else:
489
+ raise FileNotFoundError(f"UNet checkpoint not found at {latest_checkpoint}")
490
+
491
+ if lora_name:
492
+ # ... (Код LoRA без изменений, опущен для краткости, если не используется, иначе раскомменти��уйте оригинальный блок) ...
493
+ pass
494
+
495
+ # Оптимизатор
496
+ if lora_name:
497
+ trainable_params = [p for p in unet.parameters() if p.requires_grad]
498
+ else:
499
+ if fbp:
500
+ trainable_params = list(unet.parameters())
501
+
502
+
503
+ def create_optimizer(name, params):
504
+ if name == "adam8bit":
505
+ return bnb.optim.AdamW8bit(
506
+ params, lr=base_learning_rate, betas=(0.9, betta2), eps=eps, weight_decay=0.01,
507
+ percentile_clipping=percentile_clipping
508
+ )
509
+ elif name == "adam":
510
+ return torch.optim.AdamW(
511
+ params, lr=base_learning_rate, betas=(0.9, betta2), eps=1e-8, weight_decay=0.01
512
+ )
513
+ elif name == "muon":
514
+ from muon import MuonWithAuxAdam
515
+ trainable_params = [p for p in params if p.requires_grad]
516
+ hidden_weights = [p for p in trainable_params if p.ndim >= 2]
517
+ hidden_gains_biases = [p for p in trainable_params if p.ndim < 2]
518
+
519
+ param_groups = [
520
+ dict(params=hidden_weights, use_muon=True,
521
+ lr=1e-3, weight_decay=1e-4),
522
+ dict(params=hidden_gains_biases, use_muon=False,
523
+ lr=1e-4, betas=(0.9, 0.95), weight_decay=1e-4),
524
+ ]
525
+ optimizer = MuonWithAuxAdam(param_groups)
526
+ from snooc import SnooC
527
+ return SnooC(optimizer)
528
+ else:
529
+ raise ValueError(f"Unknown optimizer: {name}")
530
+
531
+ if fbp:
532
+ optimizer_dict = {p: create_optimizer(optimizer_type, [p]) for p in trainable_params}
533
+ def optimizer_hook(param):
534
+ optimizer_dict[param].step()
535
+ optimizer_dict[param].zero_grad(set_to_none=True)
536
+ for param in trainable_params:
537
+ param.register_post_accumulate_grad_hook(optimizer_hook)
538
+ unet, optimizer = accelerator.prepare(unet, optimizer_dict)
539
+ else:
540
+ # 1. Сначала замораживаем ВСЕ параметры UNet
541
+ #unet.requires_grad_(False)
542
+
543
+ # 2. Размораживаем только нужные
544
+ #trainable_params_names = ["conv_in.weight", "conv_in.bias", "conv_out.weight", "conv_out.bias"]
545
+ #train_params = []
546
+
547
+ #for name, param in unet.named_parameters():
548
+ # if any(target in name for target in trainable_params_names):
549
+ # param.requires_grad = True
550
+ # train_params.append(param)
551
+ # print(f"Обучаемый слой: {name}")
552
+
553
+ # 3. Передаем в оптимизатор ТОЛЬКО обучаемые параметры
554
+ #optimizer = create_optimizer(optimizer_type, train_params)
555
+
556
+ unet.requires_grad_(True)
557
+ optimizer = create_optimizer(optimizer_type, unet.parameters())
558
+
559
+ def lr_schedule(step):
560
+ x = step / (total_training_steps * world_size)
561
+ warmup = warmup_percent
562
+ if not use_decay:
563
+ return base_learning_rate
564
+ if x < warmup:
565
+ return min_learning_rate + (base_learning_rate - min_learning_rate) * (x / warmup)
566
+ decay_ratio = (x - warmup) / (1 - warmup)
567
+ return min_learning_rate + 0.5 * (base_learning_rate - min_learning_rate) * \
568
+ (1 + math.cos(math.pi * decay_ratio))
569
+ lr_scheduler = LambdaLR(optimizer, lambda step: lr_schedule(step) / base_learning_rate)
570
+ unet, optimizer, lr_scheduler = accelerator.prepare(unet, optimizer, lr_scheduler)
571
+
572
+ if torch_compile:
573
+ print("compiling")
574
+ unet = torch.compile(unet)
575
+ print("compiling - ok")
576
+
577
+ # Фиксированные семплы
578
+ fixed_samples = get_fixed_samples_by_resolution(dataset)
579
+
580
+ # --- [UPDATED] Функция для негативного эмбеддинга (возвращает 3 элемента) ---
581
+ def get_negative_embedding(neg_prompt="", batch_size=1):
582
+ if not neg_prompt:
583
+ hidden_dim = 2048
584
+ seq_len = max_length
585
+ empty_emb = torch.zeros((batch_size, seq_len, hidden_dim), dtype=dtype, device=device)
586
+ empty_mask = torch.ones((batch_size, seq_len), dtype=torch.int64, device=device)
587
+ return empty_emb, empty_mask
588
+
589
+ uncond_emb, uncond_mask = encode_texts([neg_prompt])
590
+ uncond_emb = uncond_emb.to(dtype=dtype, device=device).repeat(batch_size, 1, 1)
591
+ uncond_mask = uncond_mask.to(device=device).repeat(batch_size, 1)
592
+
593
+ return uncond_emb, uncond_mask
594
+
595
+ # Получаем негативные (пустые) условия для валидации
596
+ uncond_emb, uncond_mask = get_negative_embedding("low quality")
597
+
598
+ # --- Функция генерации семплов ---
599
+ @torch.compiler.disable()
600
+ @torch.no_grad()
601
+ def generate_and_save_samples(fixed_samples_cpu, uncond_data, step):
602
+ uncond_emb, uncond_mask = uncond_data
603
+
604
+ original_model = None
605
+ try:
606
+ if not torch_compile:
607
+ original_model = accelerator.unwrap_model(unet, keep_torch_compile=True).eval()
608
+ else:
609
+ original_model = unet.eval()
610
+
611
+ vae.to(device=device).eval()
612
+
613
+ all_generated_images = []
614
+ all_captions = []
615
+
616
+ # Распаковываем 5 элементов (добавились mask)
617
+ for size, (sample_latents, sample_text_embeddings, sample_mask, sample_text) in fixed_samples_cpu.items():
618
+ width, height = size
619
+ sample_latents = sample_latents.to(dtype=dtype, device=device)
620
+ sample_text_embeddings = sample_text_embeddings.to(dtype=dtype, device=device)
621
+ sample_mask = sample_mask.to(device=device)
622
+
623
+ latents = torch.randn(
624
+ sample_latents.shape,
625
+ device=device,
626
+ dtype=sample_latents.dtype,
627
+ generator=torch.Generator(device=device).manual_seed(seed)
628
+ )
629
+
630
+ scheduler.set_timesteps(n_diffusion_steps, device=device)
631
+
632
+ for t in scheduler.timesteps:
633
+ if guidance_scale != 1:
634
+ latent_model_input = torch.cat([latents, latents], dim=0)
635
+
636
+ # Подготовка батчей для CFG (Negative + Positive)
637
+ # 1. Embeddings
638
+ curr_batch_size = sample_text_embeddings.shape[0]
639
+ seq_len = sample_text_embeddings.shape[1]
640
+ hidden_dim = sample_text_embeddings.shape[2]
641
+
642
+ neg_emb_batch = uncond_emb[0:1].expand(curr_batch_size, -1, -1)
643
+ text_embeddings_batch = torch.cat([neg_emb_batch, sample_text_embeddings], dim=0)
644
+
645
+ # 2. Masks
646
+ neg_mask_batch = uncond_mask[0:1].expand(curr_batch_size, -1)
647
+ attention_mask_batch = torch.cat([neg_mask_batch, sample_mask], dim=0)
648
+
649
+ else:
650
+ latent_model_input = latents
651
+ text_embeddings_batch = sample_text_embeddings
652
+ attention_mask_batch = sample_mask
653
+
654
+ # Предсказание с передачей всех условий
655
+ model_out = original_model(
656
+ latent_model_input,
657
+ t,
658
+ encoder_hidden_states=text_embeddings_batch,
659
+ encoder_attention_mask=attention_mask_batch,
660
+ )
661
+ flow = getattr(model_out, "sample", model_out)
662
+
663
+ if guidance_scale != 1:
664
+ flow_uncond, flow_cond = flow.chunk(2)
665
+ flow = flow_uncond + guidance_scale * (flow_cond - flow_uncond)
666
+
667
+ latents = scheduler.step(flow, t, latents).prev_sample
668
+
669
+ current_latents = latents
670
+ if step==0:
671
+ current_latents = sample_latents
672
+
673
+ latent_for_vae = current_latents.detach() * scaling_factor + shift_factor
674
+ decoded = vae.decode(latent_for_vae.to(torch.float32)).sample
675
+ decoded_fp32 = decoded.to(torch.float32)
676
+
677
+ for img_idx, img_tensor in enumerate(decoded_fp32):
678
+ img = (img_tensor / 2 + 0.5).clamp(0, 1).cpu().numpy()
679
+ img = img.transpose(1, 2, 0)
680
+
681
+ if np.isnan(img).any():
682
+ print("NaNs found, saving stopped! Step:", step)
683
+ pil_img = Image.fromarray((img * 255).astype("uint8"))
684
+
685
+ max_w_overall = max(s[0] for s in fixed_samples_cpu.keys())
686
+ max_h_overall = max(s[1] for s in fixed_samples_cpu.keys())
687
+ max_w_overall = max(255, max_w_overall)
688
+ max_h_overall = max(255, max_h_overall)
689
+
690
+ padded_img = ImageOps.pad(pil_img, (max_w_overall, max_h_overall), color='white')
691
+ all_generated_images.append(padded_img)
692
+
693
+ caption_text = sample_text[img_idx][:300] if img_idx < len(sample_text) else ""
694
+ all_captions.append(caption_text)
695
+
696
+ sample_path = f"{generated_folder}/{project}_{width}x{height}_{img_idx}.jpg"
697
+ pil_img.save(sample_path, "JPEG", quality=96)
698
+
699
+ if use_wandb and accelerator.is_main_process:
700
+ wandb_images = [
701
+ wandb.Image(img, caption=f"{all_captions[i]}")
702
+ for i, img in enumerate(all_generated_images)
703
+ ]
704
+ wandb.log({"generated_images": wandb_images})
705
+ if use_comet_ml and accelerator.is_main_process:
706
+ for i, img in enumerate(all_generated_images):
707
+ comet_experiment.log_image(
708
+ image_data=img,
709
+ name=f"step_{step}_img_{i}",
710
+ step=step,
711
+ metadata={"caption": all_captions[i]}
712
+ )
713
+ finally:
714
+ vae.to("cpu")
715
+ torch.cuda.empty_cache()
716
+ gc.collect()
717
+
718
+ # --------------------------- Генерация сэмплов перед обучением ---------------------------
719
+ if accelerator.is_main_process:
720
+ if save_model:
721
+ print("Генерация сэмплов до старта обучения...")
722
+ generate_and_save_samples(fixed_samples, (uncond_emb, uncond_mask), 0)
723
+ accelerator.wait_for_everyone()
724
+
725
+ def save_checkpoint(unet, variant=""):
726
+ if accelerator.is_main_process:
727
+ if lora_name:
728
+ save_lora_checkpoint(unet)
729
+ else:
730
+ model_to_save = None
731
+ if not torch_compile:
732
+ model_to_save = accelerator.unwrap_model(unet)
733
+ else:
734
+ model_to_save = unet
735
+
736
+ if variant != "":
737
+ model_to_save.to(dtype=torch.float16).save_pretrained(
738
+ os.path.join(checkpoints_folder, f"{project}"), variant=variant
739
+ )
740
+ else:
741
+ model_to_save.save_pretrained(os.path.join(checkpoints_folder, f"{project}"))
742
+
743
+ unet = unet.to(dtype=dtype)
744
+
745
+ # --------------------------- Тренировочный цикл ---------------------------
746
+ if accelerator.is_main_process:
747
+ print(f"Total steps per GPU: {total_training_steps}")
748
+
749
+ epoch_loss_points = []
750
+ progress_bar = tqdm(total=total_training_steps, disable=not accelerator.is_local_main_process, desc="Training", unit="step")
751
+
752
+ steps_per_epoch = len(dataloader)
753
+ sample_interval = max(1, steps_per_epoch // sample_interval_share)
754
+ min_loss = 4.
755
+
756
+ for epoch in range(start_epoch, start_epoch + num_epochs):
757
+ batch_losses = []
758
+ batch_grads = []
759
+ batch_sampler.set_epoch(epoch)
760
+ accelerator.wait_for_everyone()
761
+ unet.train()
762
+
763
+ for step, (latents, embeddings, attention_mask) in enumerate(dataloader):
764
+ with accelerator.accumulate(unet):
765
+ if save_model == False and epoch == 0 and step == 5 :
766
+ used_gb = torch.cuda.max_memory_allocated() / 1024**3
767
+ print(f"Шаг {step}: {used_gb:.2f} GB")
768
+
769
+ # шум
770
+ noise = torch.randn_like(latents, dtype=latents.dtype)
771
+
772
+ # 3. Время t (сэмплим, как и раньше, но чуть сжимаем края)
773
+ u = torch.rand(latents.shape[0], device=latents.device, dtype=latents.dtype)
774
+ t = u * (1 - 2 * 1e-5) + 1e-5 # Теперь t строго в (0.00001 ... 0.99999)
775
+ # интерполяция между x0 и шумом
776
+ noisy_latents = (1.0 - t.view(-1, 1, 1, 1)) * latents + t.view(-1, 1, 1, 1) * noise
777
+ # делаем integer timesteps для UNet
778
+ timesteps = t.to(torch.float32).mul(999.0)
779
+ timesteps = timesteps.clamp(0, scheduler.config.num_train_timesteps - 1)
780
+
781
+ # --- Вызов UNet с маской ---
782
+ model_pred = unet(
783
+ noisy_latents,
784
+ timesteps,
785
+ encoder_hidden_states=embeddings,
786
+ encoder_attention_mask=attention_mask
787
+ ).sample
788
+
789
+ target = noise - latents
790
+
791
+ mse_loss = F.mse_loss(model_pred.float(), target.float())
792
+ mae_loss = F.l1_loss(model_pred.float(), target.float())
793
+ batch_losses.append(mse_loss.detach().item())
794
+
795
+ if (global_step % 100 == 0) or (global_step % sample_interval == 0):
796
+ accelerator.wait_for_everyone()
797
+
798
+ losses_dict = {}
799
+ losses_dict["mse"] = mse_loss
800
+ losses_dict["mae"] = mae_loss
801
+
802
+ # === Нормализация всех лоссов ===
803
+ abs_for_norm = {k: losses_dict.get(k, torch.tensor(0.0, device=device)) for k in normalizer.ratios.keys()}
804
+ total_loss, coeffs, meds = normalizer.update_and_total(abs_for_norm)
805
+
806
+ if (global_step % 100 == 0) or (global_step % sample_interval == 0):
807
+ accelerator.wait_for_everyone()
808
+
809
+ accelerator.backward(total_loss)
810
+
811
+ if (global_step % 100 == 0) or (global_step % sample_interval == 0):
812
+ accelerator.wait_for_everyone()
813
+
814
+ grad = 0.0
815
+ if not fbp:
816
+ if accelerator.sync_gradients:
817
+ #with torch.amp.autocast('cuda', enabled=False):
818
+ grad_val = accelerator.clip_grad_norm_(unet.parameters(), clip_grad_norm)
819
+ grad = float(grad_val)
820
+ optimizer.step()
821
+ lr_scheduler.step()
822
+ optimizer.zero_grad(set_to_none=True)
823
+
824
+ if accelerator.sync_gradients:
825
+ global_step += 1
826
+ progress_bar.update(1)
827
+ if accelerator.is_main_process:
828
+ if fbp:
829
+ current_lr = base_learning_rate
830
+ else:
831
+ current_lr = lr_scheduler.get_last_lr()[0]
832
+ batch_grads.append(grad)
833
+
834
+ log_data = {}
835
+ log_data["loss_mse"] = mse_loss.detach().item()
836
+ log_data["loss_mae"] = mae_loss.detach().item()
837
+ log_data["lr"] = current_lr
838
+ log_data["grad"] = grad
839
+ log_data["loss_norm"] = float(total_loss.item())
840
+ for k, c in coeffs.items():
841
+ log_data[f"coeff_{k}"] = float(c)
842
+ if accelerator.sync_gradients:
843
+ if use_wandb:
844
+ wandb.log(log_data, step=global_step)
845
+ if use_comet_ml:
846
+ comet_experiment.log_metrics(log_data, step=global_step)
847
+
848
+ if global_step % sample_interval == 0:
849
+ # Передаем tuple (emb, mask) для негатива
850
+ if save_model:
851
+ generate_and_save_samples(fixed_samples, (uncond_emb, uncond_mask), global_step)
852
+ elif epoch % 10 == 0:
853
+ generate_and_save_samples(fixed_samples, (uncond_emb, uncond_mask), global_step)
854
+ last_n = sample_interval
855
+
856
+ if save_model:
857
+ has_losses = len(batch_losses) > 0
858
+ avg_sample_loss = np.mean(batch_losses[-sample_interval:]) if has_losses else 0.0
859
+ last_loss = batch_losses[-1] if has_losses else 0.0
860
+ max_loss = max(avg_sample_loss, last_loss)
861
+ should_save = max_loss < min_loss * save_barrier
862
+ print(
863
+ f"Saving: {should_save} | Max: {max_loss:.4f} | "
864
+ f"Last: {last_loss:.4f} | Avg: {avg_sample_loss:.4f}"
865
+ )
866
+ # 6. Сохранение и обновление
867
+ if should_save:
868
+ min_loss = max_loss
869
+ save_checkpoint(unet)
870
+
871
+ if accelerator.is_main_process:
872
+ avg_epoch_loss = np.mean(batch_losses) if len(batch_losses) > 0 else 0.0
873
+ avg_epoch_grad = np.mean(batch_grads) if len(batch_grads) > 0 else 0.0
874
+
875
+ print(f"\nЭпоха {epoch} завершена. Средний лосс: {avg_epoch_loss:.6f}")
876
+ log_data_ep = {
877
+ "epoch_loss": avg_epoch_loss,
878
+ "epoch_grad": avg_epoch_grad,
879
+ "epoch": epoch + 1,
880
+ }
881
+ if use_wandb:
882
+ wandb.log(log_data_ep)
883
+ if use_comet_ml:
884
+ comet_experiment.log_metrics(log_data_ep)
885
+
886
+ if accelerator.is_main_process:
887
+ print("Обучение завершено! Сохраняем финальную модель...")
888
+ #if save_model:
889
+ save_checkpoint(unet,"fp16")
890
+ if use_comet_ml:
891
+ comet_experiment.end()
892
+ accelerator.free_memory()
893
+ if torch.distributed.is_initialized():
894
+ torch.distributed.destroy_process_group()
895
+
896
+ print("Готово!")