Text-to-Image
Diffusers
Safetensors
recoilme commited on
Commit
ae9c446
·
1 Parent(s): 741b3a0
Files changed (1) hide show
  1. pipeline_sdxs.py +181 -69
pipeline_sdxs.py CHANGED
@@ -22,15 +22,108 @@ class SdxsPipeline(DiffusionPipeline):
22
  unet=unet,
23
  scheduler=scheduler
24
  )
25
- self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- # --- ВСПОМОГАТЕЛЬНАЯ ФУНКЦИЯ ДЛЯ ПОДГОТОВКИ ИЗОБРАЖЕНИЯ (Img2Img) ---
28
  def preprocess_image(self, image: Image.Image, width: int, height: int):
29
- """Ресайз и центрированный кроп изображения под нужный размер"""
30
- height = (height // self.vae_scale_factor) * self.vae_scale_factor // 2
31
- width= (width // self.vae_scale_factor) * self.vae_scale_factor // 2
 
 
32
  w, h = image.size
33
- aspect_ratio = width / height
 
34
  if w / h > aspect_ratio:
35
  new_w = int(h * aspect_ratio)
36
  left = (w - new_w) // 2
@@ -40,11 +133,11 @@ class SdxsPipeline(DiffusionPipeline):
40
  top = (h - new_h) // 2
41
  image = image.crop((0, top, w, top + new_h))
42
 
43
- image = image.resize((width, height ), resample=Image.LANCZOS)
44
  image = np.array(image).astype(np.float32) / 255.0
45
- image = image[None].transpose(0, 3, 1, 2) # [1, C, H, W]
46
  image = torch.from_numpy(image)
47
- return 2.0 * image - 1.0 # В диапазон [-1, 1]
48
 
49
  def encode_prompt(self, prompt, negative_prompt, device, dtype):
50
  def get_single_encode(texts, is_negative=False):
@@ -107,6 +200,7 @@ class SdxsPipeline(DiffusionPipeline):
107
  generator: Optional[torch.Generator] = None,
108
  output_type: str = "pil",
109
  return_dict: bool = True,
 
110
  **kwargs,
111
  ):
112
  device = self.device
@@ -122,31 +216,18 @@ class SdxsPipeline(DiffusionPipeline):
122
  self.scheduler.set_timesteps(num_inference_steps, device=device)
123
  timesteps = self.scheduler.timesteps
124
 
125
- # ИСПРАВЛЕННАЯ ЛОГИКА IMG2IMG
126
  if image is not None:
127
- # Подготовка изображения
128
  if isinstance(image, Image.Image):
129
- image = [image]
130
-
131
- # Обработка каждого изображения
132
- image_tensors = []
133
- for img in image:
134
- img_tensor = self.preprocess_image(img, width, height)
135
- image_tensors.append(img_tensor)
136
-
137
- image_tensor = torch.cat(image_tensors, dim=0).to(
138
- device=device, dtype=self.vae.dtype
139
- )
140
-
141
- # Проверка batch size
142
- if image_tensor.shape[0] != batch_size:
143
- if image_tensor.shape[0] == 1:
144
- image_tensor = image_tensor.repeat(batch_size, 1, 1, 1)
145
- else:
146
- raise ValueError(
147
- f"Batch size mismatch: prompt has {batch_size} elements, "
148
- f"but image has {image_tensor.shape[0]} elements"
149
- )
150
 
151
  # Кодируем в латенты
152
  latents_clean = self.vae.encode(image_tensor).latent_dist.sample(generator=generator)
@@ -155,56 +236,66 @@ class SdxsPipeline(DiffusionPipeline):
155
  latents_clean = (latents_clean - vae_shift_factor) / vae_scaling_factor
156
  latents_clean = latents_clean.to(dtype=dtype)
157
 
158
- # Определяем начальный таймстеп
159
- # Flow matching: x_t = (1-t)x_0 + t*eps
160
- # coef=1.0 -> t=1.0 (полный шум)
161
- # coef=0.0 -> t=0.0 (оригинал)
162
-
163
- # Если scheduler использует [0, 1] диапазон
164
- t_start = coef # или 1.0 - coef в зависимости от направления
165
 
166
  # Находим ближайший шаг в расписании
167
- # Некоторые scheduler имеют reversed порядок (от 1 к 0)
168
- if timesteps[0] > timesteps[-1]:
169
- # Обычный порядок: от шума к данным
170
  idx = torch.searchsorted(timesteps, t_start, right=True)
171
- idx = min(idx, len(timesteps) - 1)
172
- else:
173
- # Обратный порядок: от данных к шуму
174
  idx = torch.searchsorted(timesteps.flip(0), 1.0 - t_start, right=True)
175
- idx = len(timesteps) - 1 - min(idx, len(timesteps) - 1)
176
 
177
  timesteps = timesteps[idx:]
178
 
179
- # Генерируем шум и применя flow matching формулу
180
- noise = torch.randn(
181
- latents_clean.shape,
182
- generator=generator,
183
- device=device,
184
- dtype=dtype
 
 
 
 
 
 
 
 
 
185
  )
186
 
 
 
 
 
 
187
  # Берём актуальное время из scheduler
188
  actual_t = timesteps[0].item() if isinstance(timesteps[0], torch.Tensor) else timesteps[0]
189
 
190
- # Применяем формулу flow matching
191
- latents = (1.0 - actual_t) * latents_clean + actual_t * noise
192
 
193
  else:
194
- # TXT2IMG: начинаем с чистого шума
195
  vae_scaling_factor = getattr(self.vae.config, "scaling_factor", 1.0)
196
  vae_shift_factor = getattr(self.vae.config, "shift_factor", 0.0)
197
 
 
 
 
 
198
  latents = torch.randn(
199
- (batch_size, self.unet.config.in_channels,
200
- height // self.vae_scale_factor,
201
- width // self.vae_scale_factor),
202
  generator=generator,
203
  device=device,
204
  dtype=dtype
205
  )
206
 
207
- # 3. Denoising Loop
208
  for i, t in enumerate(tqdm(timesteps, desc="Sampling")):
209
  # CFG preparation
210
  latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1 else latents
@@ -226,23 +317,44 @@ class SdxsPipeline(DiffusionPipeline):
226
  # Euler step для flow matching
227
  latents = self.scheduler.step(model_out, t, latents, return_dict=False)[0]
228
 
229
- # 4. Decode
230
  if output_type == "latent":
231
  return SdxsPipelineOutput(images=latents)
232
 
233
- # Масштабируем обратно
234
  latents = latents * vae_scaling_factor + vae_shift_factor
235
- image = self.vae.decode(latents.to(self.vae.dtype), return_dict=False)[0]
 
 
 
 
 
 
236
 
237
- # Пост-процессинг
238
- image = (image / 2 + 0.5).clamp(0, 1)
239
- image = image.cpu().permute(0, 2, 3, 1).float().numpy()
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
  if output_type == "pil":
242
- image = (image * 255).round().astype("uint8")
243
- image = [Image.fromarray(img) for img in image]
 
 
244
 
245
  if not return_dict:
246
- return image
247
 
248
- return SdxsPipelineOutput(images=image)
 
22
  unet=unet,
23
  scheduler=scheduler
24
  )
25
+ # Для асимметричного VAE: 8 энкодер, 16 декодер
26
+ self.encoder_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) # 8
27
+ self.decoder_scale_factor = self.encoder_scale_factor * 2 # 16 (если декодер в 2 раза больше)
28
+
29
+ def create_frequency_soft_cutoff_mask(self, height: int, width: int, cutoff_radius: float,
30
+ transition_width: float = 5.0, device: torch.device = None) -> torch.Tensor:
31
+ """Создает плавную маску частотного среза для сохранения структуры."""
32
+ if device is None:
33
+ device = torch.device('cpu')
34
+
35
+ u = torch.arange(height, device=device)
36
+ v = torch.arange(width, device=device)
37
+ u, v = torch.meshgrid(u, v, indexing='ij')
38
+
39
+ center_u, center_v = height // 2, width // 2
40
+ frequency_radius = torch.sqrt((u - center_u)**2 + (v - center_v)**2)
41
+
42
+ mask = torch.exp(-(frequency_radius - cutoff_radius)**2 / (2 * transition_width**2))
43
+ mask = torch.where(frequency_radius <= cutoff_radius, torch.ones_like(mask), mask)
44
+
45
+ return mask
46
+
47
+ def generate_structured_noise(
48
+ self,
49
+ image_latents: torch.Tensor,
50
+ cutoff_radius: Optional[float] = None,
51
+ transition_width: float = 2.0,
52
+ noise_std: float = 1.0,
53
+ ) -> torch.Tensor:
54
+ """
55
+ Генерирует структурированный шум для латентов с сохранением низкочастотной структуры.
56
+
57
+ Args:
58
+ image_latents: Чистые латенты изображения [B, C, H, W]
59
+ cutoff_radius: Радиус среза частот (None = авто-расчет на основе coef)
60
+ transition_width: Ширина плавного перехода
61
+ noise_std: Стандартное отклонение шума
62
+
63
+ Returns:
64
+ Структурированный шум с той же размерностью
65
+ """
66
+ batch_size, channels, height, width = image_latents.shape
67
+ device = image_latents.device
68
+ dtype = image_latents.dtype
69
+
70
+ # Автоматический расчет cutoff_radius если не задан
71
+ if cutoff_radius is None:
72
+ # Сохраняем больше низких частот для лучшей структуры
73
+ max_radius = min(height, width) / 2
74
+ cutoff_radius = max_radius * 0.7 # Сохраняем 70% низких частот
75
+
76
+ # Создаем частотную маску
77
+ freq_mask = self.create_frequency_soft_cutoff_mask(
78
+ height, width, cutoff_radius, transition_width, device
79
+ )
80
+ freq_mask = freq_mask.unsqueeze(0).unsqueeze(0) # [1, 1, H, W]
81
+
82
+ # Преобразуем латенты в частотную область
83
+ fft_image = torch.fft.fft2(image_latents, dim=(-2, -1))
84
+ fft_shifted = torch.fft.fftshift(fft_image, dim=(-2, -1))
85
+
86
+ # Извлекаем фазу изображения
87
+ image_phase = torch.angle(fft_shifted)
88
+
89
+ # Генерируем гауссовский шум
90
+ noise = torch.randn_like(image_latents) * noise_std
91
+
92
+ # Преобразуем шум в частотную область
93
+ fft_noise = torch.fft.fft2(noise, dim=(-2, -1))
94
+ fft_noise_shifted = torch.fft.fftshift(fft_noise, dim=(-2, -1))
95
+
96
+ # Извлекаем амплитуду шума
97
+ noise_magnitude = torch.abs(fft_noise_shifted)
98
+ noise_phase = torch.angle(fft_noise_shifted)
99
+
100
+ # Смешиваем фазы: низкие частоты - фаза изображения, высокие - фаза шума
101
+ mixed_phase = freq_mask * image_phase + (1 - freq_mask) * noise_phase
102
+
103
+ # Собираем обратно: амплитуда шума + смешанная фаза
104
+ fft_combined = noise_magnitude * torch.exp(1j * mixed_phase)
105
+ fft_unshifted = torch.fft.ifftshift(fft_combined, dim=(-2, -1))
106
+
107
+ # Обратное преобразование
108
+ structured_noise = torch.fft.ifft2(fft_unshifted, dim=(-2, -1))
109
+ structured_noise = torch.real(structured_noise)
110
+
111
+ # Нормализуем для сохранения статистики гауссовского шума
112
+ current_std = torch.std(structured_noise)
113
+ if current_std > 0:
114
+ structured_noise = structured_noise / current_std * noise_std
115
+
116
+ return structured_noise.to(dtype)
117
 
 
118
  def preprocess_image(self, image: Image.Image, width: int, height: int):
119
+ """Ресайз и центрированный кроп изображения для асимметричного VAE."""
120
+ # Для энкодера с масштабом 8
121
+ target_height = (height // self.encoder_scale_factor) * self.encoder_scale_factor
122
+ target_width = (width // self.encoder_scale_factor) * self.encoder_scale_factor
123
+
124
  w, h = image.size
125
+ aspect_ratio = target_width / target_height
126
+
127
  if w / h > aspect_ratio:
128
  new_w = int(h * aspect_ratio)
129
  left = (w - new_w) // 2
 
133
  top = (h - new_h) // 2
134
  image = image.crop((0, top, w, top + new_h))
135
 
136
+ image = image.resize((target_width, target_height), resample=Image.LANCZOS)
137
  image = np.array(image).astype(np.float32) / 255.0
138
+ image = image[None].transpose(0, 3, 1, 2) # [1, C, H, W]
139
  image = torch.from_numpy(image)
140
+ return 2.0 * image - 1.0 # [-1, 1]
141
 
142
  def encode_prompt(self, prompt, negative_prompt, device, dtype):
143
  def get_single_encode(texts, is_negative=False):
 
200
  generator: Optional[torch.Generator] = None,
201
  output_type: str = "pil",
202
  return_dict: bool = True,
203
+ structure_preservation: float = 0.7, # Новый параметр: сохранение структуры 0-1
204
  **kwargs,
205
  ):
206
  device = self.device
 
216
  self.scheduler.set_timesteps(num_inference_steps, device=device)
217
  timesteps = self.scheduler.timesteps
218
 
219
+ # 3. Обработка img2img с структурированным шумом
220
  if image is not None:
221
+ # Подготовка изображения (только одно изображение, не дублируем)
222
  if isinstance(image, Image.Image):
223
+ image_tensor = self.preprocess_image(image, width, height).to(
224
+ device=device, dtype=self.vae.dtype
225
+ )
226
+ else:
227
+ # Если список, берем первое
228
+ image_tensor = self.preprocess_image(image[0], width, height).to(
229
+ device=device, dtype=self.vae.dtype
230
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
231
 
232
  # Кодируем в латенты
233
  latents_clean = self.vae.encode(image_tensor).latent_dist.sample(generator=generator)
 
236
  latents_clean = (latents_clean - vae_shift_factor) / vae_scaling_factor
237
  latents_clean = latents_clean.to(dtype=dtype)
238
 
239
+ # Определяем начальный таймстеп для flow matching
240
+ # coef контролирует силу: 1.0 - максимальное изменение, 0.0 - минимальное
241
+ t_start = coef
 
 
 
 
242
 
243
  # Находим ближайший шаг в расписании
244
+ # Предполагаем, что timesteps идут от 1 (шум) к 0 (данные)
245
+ if timesteps[0] > timesteps[-1]: # Обычный порядок: от шума к данным
 
246
  idx = torch.searchsorted(timesteps, t_start, right=True)
247
+ idx = min(idx.item(), len(timesteps) - 1)
248
+ else: # Обратный порядок: от данных к шуму
 
249
  idx = torch.searchsorted(timesteps.flip(0), 1.0 - t_start, right=True)
250
+ idx = len(timesteps) - 1 - min(idx.item(), len(timesteps) - 1)
251
 
252
  timesteps = timesteps[idx:]
253
 
254
+ # Генерируем структурированный шум вместо обычного гауссовского
255
+ # structure_preservation контролирует сколько низких частот сохранить
256
+ # 1.0 - максимальное сохранение, 0.0 - минимальное
257
+
258
+ # Автоматический расчет cutoff_radius на основе structure_preservation
259
+ latent_height, latent_width = latents_clean.shape[-2], latents_clean.shape[-1]
260
+ max_radius = min(latent_height, latent_width) / 2
261
+ cutoff_radius = max_radius * structure_preservation
262
+
263
+ # Генерируем структурированный шум
264
+ structured_noise = self.generate_structured_noise(
265
+ image_latents=latents_clean,
266
+ cutoff_radius=cutoff_radius,
267
+ transition_width=2.0,
268
+ noise_std=1.0
269
  )
270
 
271
+ # Проверяем, что шум имеет правильную статистику
272
+ current_std = torch.std(structured_noise)
273
+ if current_std > 0:
274
+ structured_noise = structured_noise / current_std # Нормализуем к std=1
275
+
276
  # Берём актуальное время из scheduler
277
  actual_t = timesteps[0].item() if isinstance(timesteps[0], torch.Tensor) else timesteps[0]
278
 
279
+ # Применяем формулу flow matching со структурированным шумом
280
+ latents = (1.0 - actual_t) * latents_clean + actual_t * structured_noise
281
 
282
  else:
283
+ # TXT2IMG: начинаем с чистого гауссовского шума
284
  vae_scaling_factor = getattr(self.vae.config, "scaling_factor", 1.0)
285
  vae_shift_factor = getattr(self.vae.config, "shift_factor", 0.0)
286
 
287
+ # Для декодера с масштабом 16
288
+ latent_height = height // self.decoder_scale_factor
289
+ latent_width = width // self.decoder_scale_factor
290
+
291
  latents = torch.randn(
292
+ (batch_size, self.unet.config.in_channels, latent_height, latent_width),
 
 
293
  generator=generator,
294
  device=device,
295
  dtype=dtype
296
  )
297
 
298
+ # 4. Denoising Loop
299
  for i, t in enumerate(tqdm(timesteps, desc="Sampling")):
300
  # CFG preparation
301
  latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1 else latents
 
317
  # Euler step для flow matching
318
  latents = self.scheduler.step(model_out, t, latents, return_dict=False)[0]
319
 
320
+ # 5. Decode
321
  if output_type == "latent":
322
  return SdxsPipelineOutput(images=latents)
323
 
324
+ # Масштабируем обратно для VAE
325
  latents = latents * vae_scaling_factor + vae_shift_factor
326
+ image_output = self.vae.decode(latents.to(self.vae.dtype), return_dict=False)[0]
327
+
328
+ # Исправляем инвертированное изображение - правильная нормализация
329
+ # VAE обычно выдает в диапазоне [-1, 1] или [0, 1]
330
+ # Проверяем диапазон и нормализуем правильно
331
+ image_min = image_output.min()
332
+ image_max = image_output.max()
333
 
334
+ if image_min < -1.5 and image_max > 1.5: # Вероятно диапазон [-∞, ∞]
335
+ image_output = torch.tanh(image_output) # Приводим к [-1, 1]
336
+
337
+ # Нормализуем к [0, 1] для PIL
338
+ image_output = (image_output.clamp(-1, 1) + 1) / 2
339
+
340
+ # Для асимметричного VAE: выход декодера больше, возможно нужно ресайзить
341
+ output_height, output_width = image_output.shape[-2:]
342
+ if output_height != height or output_width != width:
343
+ # Простой ресайз до нужного размера
344
+ image_output = torch.nn.functional.interpolate(
345
+ image_output, size=(height, width), mode='bicubic', align_corners=False
346
+ )
347
+
348
+ image_output = image_output.clamp(0, 1)
349
+ image_np = image_output.cpu().permute(0, 2, 3, 1).float().numpy()
350
 
351
  if output_type == "pil":
352
+ image_np = (image_np * 255).round().astype("uint8")
353
+ images = [Image.fromarray(img) for img in image_np]
354
+ else:
355
+ images = image_np
356
 
357
  if not return_dict:
358
+ return images
359
 
360
+ return SdxsPipelineOutput(images=images)