Text-to-Image
Diffusers
Safetensors
recoilme commited on
Commit
741b3a0
·
1 Parent(s): 9dbef7d
Files changed (2) hide show
  1. pipeline_sdxs-Copy2.py +196 -0
  2. pipeline_sdxs.py +94 -42
pipeline_sdxs-Copy2.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ from PIL import Image
4
+ from typing import List, Union, Optional, Tuple
5
+ from dataclasses import dataclass
6
+
7
+ from diffusers import DiffusionPipeline
8
+ from diffusers.utils import BaseOutput
9
+ from tqdm import tqdm
10
+
11
+ @dataclass
12
+ class SdxsPipelineOutput(BaseOutput):
13
+ images: Union[List[Image.Image], np.ndarray]
14
+
15
+ class SdxsPipeline(DiffusionPipeline):
16
+ def __init__(self, vae, text_encoder, tokenizer, unet, scheduler):
17
+ super().__init__()
18
+ self.register_modules(
19
+ vae=vae,
20
+ text_encoder=text_encoder,
21
+ tokenizer=tokenizer,
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
37
+ image = image.crop((left, 0, left + new_w, h))
38
+ else:
39
+ new_h = int(w / aspect_ratio)
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):
51
+ if texts is None or texts == "":
52
+ hidden_dim = self.text_encoder.config.hidden_size
53
+ shape = (1, self.text_encoder.config.max_position_embeddings, hidden_dim)
54
+ emb = torch.zeros(shape, dtype=dtype, device=device)
55
+ mask = torch.ones((1, self.text_encoder.config.max_position_embeddings), dtype=torch.int64, device=device)
56
+ return emb, mask
57
+
58
+ if isinstance(texts, str):
59
+ texts = [texts]
60
+
61
+ with torch.no_grad():
62
+ toks = self.tokenizer(
63
+ texts,
64
+ padding="max_length",
65
+ max_length=self.text_encoder.config.max_position_embeddings,
66
+ truncation=True,
67
+ return_tensors="pt"
68
+ ).to(device)
69
+
70
+ outputs = self.text_encoder(
71
+ input_ids=toks.input_ids,
72
+ attention_mask=toks.attention_mask,
73
+ output_hidden_states=True
74
+ )
75
+
76
+ layer_index = -2
77
+ prompt_embeds = outputs.hidden_states[layer_index]
78
+ final_layer_norm = self.text_encoder.text_model.final_layer_norm
79
+ prompt_embeds = final_layer_norm(prompt_embeds)
80
+
81
+ return prompt_embeds, toks.attention_mask
82
+
83
+ pos_embeds, pos_mask = get_single_encode(prompt)
84
+ neg_embeds, neg_mask = get_single_encode(negative_prompt, is_negative=True)
85
+
86
+ batch_size = pos_embeds.shape[0]
87
+ if neg_embeds.shape[0] != batch_size:
88
+ neg_embeds = neg_embeds.repeat(batch_size, 1, 1)
89
+ neg_mask = neg_mask.repeat(batch_size, 1)
90
+
91
+ text_embeddings = torch.cat([neg_embeds, pos_embeds], dim=0)
92
+ final_mask = torch.cat([neg_mask, pos_mask], dim=0)
93
+
94
+ return text_embeddings.to(dtype=dtype), final_mask.to(dtype=torch.int64)
95
+
96
+ @torch.no_grad()
97
+ def __call__(
98
+ self,
99
+ prompt: Union[str, List[str]],
100
+ image: Optional[Union[Image.Image, List[Image.Image]]] = None, # Добавлен параметр изображения
101
+ coef: float = 0.5, # Коэффициент влияния (strength): 1.0 - полный шум, 0.0 - оригинал
102
+ negative_prompt: Optional[Union[str, List[str]]] = None,
103
+ height: int = 1024,
104
+ width: int = 1024,
105
+ num_inference_steps: int = 40,
106
+ guidance_scale: float = 4.0,
107
+ generator: Optional[torch.Generator] = None,
108
+ output_type: str = "pil",
109
+ return_dict: bool = True,
110
+ **kwargs,
111
+ ):
112
+ device = self.device
113
+ self.vae.to(device)
114
+ vae_scaling_factor = getattr(self.vae.config, "scaling_factor", 1.0)
115
+ vae_shift_factor = getattr(self.vae.config, "shift_factor", 0.0)
116
+ device = self.device
117
+ dtype = self.unet.dtype
118
+
119
+ # 1. Encode Prompt
120
+ text_embeddings, attention_mask = self.encode_prompt(prompt, negative_prompt, device, dtype)
121
+ batch_size = 1 if isinstance(prompt, str) else len(prompt)
122
+
123
+ # 2. Настройка таймстепов
124
+ self.scheduler.set_timesteps(num_inference_steps, device=device)
125
+ timesteps = self.scheduler.timesteps # Обычно от 1.0 до 0.001
126
+
127
+ # --- ИСПРАВЛЕННАЯ ЛОГИКА IMG2IMG ---
128
+ if image is not None:
129
+ # Превращаем картинку в латенты
130
+ image_tensor = self.preprocess_image(image, width, height).to(device=device, dtype=self.vae.dtype)
131
+
132
+ # ВАЖНО: Кодируем и применяем масштабирование VAE СРАЗУ
133
+ latents_clean = self.vae.encode(image_tensor).latent_dist.sample(generator=generator)
134
+ latents_clean = (latents_clean - vae_shift_factor) / vae_scaling_factor
135
+ latents_clean = latents_clean.to(dtype=dtype)
136
+
137
+ # Определяем начальный таймстеп t_start.
138
+ # coef=1.0 -> t=1.0 (шум), coef=0.0 -> t=0.0 (картинка)
139
+ t_start = coef
140
+
141
+ # Находим индекс шага в расписании шедулера, который ближе всего к t_start
142
+ # Это исключает "одинаковость" при разной силе
143
+ idx = (timesteps - t_start).abs().argmin().item()
144
+ timesteps = timesteps[idx:]
145
+
146
+ # Добавляем шум строго по формуле Flow Matching: x_t = (1-t)x_0 + t*eps
147
+ noise = torch.randn(latents_clean.shape, generator=generator, device=device, dtype=dtype)
148
+ actual_t = timesteps[0] # Берем точное значение из шедулера
149
+ latents = (1.0 - actual_t) * latents_clean + actual_t * noise
150
+ else:
151
+ # TXT2IMG: Начинаем с чистого шума (t=1.0)
152
+ latents = torch.randn(
153
+ (batch_size, self.unet.config.in_channels, height // self.vae_scale_factor, width // self.vae_scale_factor),
154
+ generator=generator, device=device, dtype=dtype
155
+ )
156
+
157
+ # 3. Denoising Loop
158
+ for t in tqdm(timesteps, desc="Sampling"):
159
+ latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1 else latents
160
+
161
+ # Predict
162
+ model_out = self.unet(
163
+ latent_model_input,
164
+ t,
165
+ encoder_hidden_states=text_embeddings,
166
+ encoder_attention_mask=attention_mask,
167
+ return_dict=False,
168
+ )[0]
169
+
170
+ # CFG
171
+ if guidance_scale > 1:
172
+ flow_uncond, flow_cond = model_out.chunk(2)
173
+ model_out = flow_uncond + guidance_scale * (flow_cond - flow_uncond)
174
+
175
+ # Step (Эйлеров шаг: latents = latents + (t_next - t_curr) * model_out)
176
+ latents = self.scheduler.step(model_out, t, latents, return_dict=False)[0]
177
+
178
+ # 5. Decode
179
+ if output_type == "latent":
180
+ return SdxsPipelineOutput(images=latents)
181
+
182
+ latents = latents * vae_scaling_factor + vae_shift_factor
183
+ image = self.vae.decode(latents.to(self.vae.dtype), return_dict=False)[0]
184
+
185
+ # Пост-процессинг
186
+ image = (image / 2 + 0.5).clamp(0, 1)
187
+ image = image.cpu().permute(0, 2, 3, 1).float().numpy()
188
+
189
+ if output_type == "pil":
190
+ image = (image * 255).round().astype("uint8")
191
+ image = [Image.fromarray(img) for img in image]
192
+
193
+ if not return_dict:
194
+ return image
195
+
196
+ return SdxsPipelineOutput(images=image)
pipeline_sdxs.py CHANGED
@@ -97,8 +97,8 @@ class SdxsPipeline(DiffusionPipeline):
97
  def __call__(
98
  self,
99
  prompt: Union[str, List[str]],
100
- image: Optional[Union[Image.Image, List[Image.Image]]] = None, # Добавлен параметр изображения
101
- coef: float = 0.5, # Коэффициент влияния (strength): 1.0 - полный шум, 0.0 - оригинал
102
  negative_prompt: Optional[Union[str, List[str]]] = None,
103
  height: int = 1024,
104
  width: int = 1024,
@@ -110,55 +110,106 @@ class SdxsPipeline(DiffusionPipeline):
110
  **kwargs,
111
  ):
112
  device = self.device
113
- self.vae.to(device)
114
- vae_scaling_factor = getattr(self.vae.config, "scaling_factor", 1.0)
115
- vae_shift_factor = getattr(self.vae.config, "shift_factor", 0.0)
116
- device = self.device
117
  dtype = self.unet.dtype
118
 
119
  # 1. Encode Prompt
120
- text_embeddings, attention_mask = self.encode_prompt(prompt, negative_prompt, device, dtype)
 
 
121
  batch_size = 1 if isinstance(prompt, str) else len(prompt)
122
-
123
  # 2. Настройка таймстепов
124
  self.scheduler.set_timesteps(num_inference_steps, device=device)
125
- timesteps = self.scheduler.timesteps # Обычно от 1.0 до 0.001
126
-
127
- # --- ИСПРАВЛЕННАЯ ЛОГИКА IMG2IMG ---
128
  if image is not None:
129
- # Превращаем картинку в латенты
130
- image_tensor = self.preprocess_image(image, width, height).to(device=device, dtype=self.vae.dtype)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- # ВАЖНО: Кодируем и применяем масштабирование VAE СРАЗУ
133
  latents_clean = self.vae.encode(image_tensor).latent_dist.sample(generator=generator)
 
 
134
  latents_clean = (latents_clean - vae_shift_factor) / vae_scaling_factor
135
  latents_clean = latents_clean.to(dtype=dtype)
136
-
137
- # Определяем начальный таймстеп t_start.
138
- # coef=1.0 -> t=1.0 (шум), coef=0.0 -> t=0.0 (картинка)
139
- t_start = coef
140
-
141
- # Находим индекс шага в расписании шедулера, который ближе всего к t_start
142
- # Это исключает динаковость" при разной силе
143
- idx = (timesteps - t_start).abs().argmin().item()
144
- timesteps = timesteps[idx:]
145
-
146
- # Добавляем шум строго по формуле Flow Matching: x_t = (1-t)x_0 + t*eps
147
- noise = torch.randn(latents_clean.shape, generator=generator, device=device, dtype=dtype)
148
- actual_t = timesteps[0] # Берем точное значение из шедулера
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  latents = (1.0 - actual_t) * latents_clean + actual_t * noise
 
150
  else:
151
- # TXT2IMG: Начинаем с чистого шума (t=1.0)
 
 
 
152
  latents = torch.randn(
153
- (batch_size, self.unet.config.in_channels, height // self.vae_scale_factor, width // self.vae_scale_factor),
154
- generator=generator, device=device, dtype=dtype
 
 
 
 
155
  )
156
-
157
  # 3. Denoising Loop
158
- for t in tqdm(timesteps, desc="Sampling"):
 
159
  latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1 else latents
160
 
161
- # Predict
162
  model_out = self.unet(
163
  latent_model_input,
164
  t,
@@ -166,31 +217,32 @@ device = self.device
166
  encoder_attention_mask=attention_mask,
167
  return_dict=False,
168
  )[0]
169
-
170
  # CFG
171
  if guidance_scale > 1:
172
  flow_uncond, flow_cond = model_out.chunk(2)
173
  model_out = flow_uncond + guidance_scale * (flow_cond - flow_uncond)
174
-
175
- # Step (Эйлеров шаг: latents = latents + (t_next - t_curr) * model_out)
176
  latents = self.scheduler.step(model_out, t, latents, return_dict=False)[0]
177
-
178
- # 5. Decode
179
  if output_type == "latent":
180
  return SdxsPipelineOutput(images=latents)
181
-
 
182
  latents = latents * vae_scaling_factor + vae_shift_factor
183
  image = self.vae.decode(latents.to(self.vae.dtype), return_dict=False)[0]
184
 
185
  # Пост-процессинг
186
  image = (image / 2 + 0.5).clamp(0, 1)
187
  image = image.cpu().permute(0, 2, 3, 1).float().numpy()
188
-
189
  if output_type == "pil":
190
  image = (image * 255).round().astype("uint8")
191
  image = [Image.fromarray(img) for img in image]
192
-
193
  if not return_dict:
194
  return image
195
-
196
  return SdxsPipelineOutput(images=image)
 
97
  def __call__(
98
  self,
99
  prompt: Union[str, List[str]],
100
+ image: Optional[Union[Image.Image, List[Image.Image]]] = None,
101
+ coef: float = 0.5, # strength: 1.0 - полный шум, 0.0 - оригинал
102
  negative_prompt: Optional[Union[str, List[str]]] = None,
103
  height: int = 1024,
104
  width: int = 1024,
 
110
  **kwargs,
111
  ):
112
  device = self.device
 
 
 
 
113
  dtype = self.unet.dtype
114
 
115
  # 1. Encode Prompt
116
+ text_embeddings, attention_mask = self.encode_prompt(
117
+ prompt, negative_prompt, device, dtype
118
+ )
119
  batch_size = 1 if isinstance(prompt, str) else len(prompt)
120
+
121
  # 2. Настройка таймстепов
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)
153
+ vae_scaling_factor = getattr(self.vae.config, "scaling_factor", 1.0)
154
+ vae_shift_factor = getattr(self.vae.config, "shift_factor", 0.0)
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
211
 
212
+ # Predict flow
213
  model_out = self.unet(
214
  latent_model_input,
215
  t,
 
217
  encoder_attention_mask=attention_mask,
218
  return_dict=False,
219
  )[0]
220
+
221
  # CFG
222
  if guidance_scale > 1:
223
  flow_uncond, flow_cond = model_out.chunk(2)
224
  model_out = flow_uncond + guidance_scale * (flow_cond - flow_uncond)
225
+
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)