Instructions to use AiArtLab/sdxs-1b with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use AiArtLab/sdxs-1b with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("AiArtLab/sdxs-1b", dtype=torch.bfloat16, device_map="cuda") prompt = "sdxs-1b" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
2601
Browse files- pipeline_sdxs-Copy1.py +176 -0
- pipeline_sdxs.py +62 -32
- samples/unet1b_320x640_0.jpg +3 -0
- samples/unet1b_352x640_0.jpg +3 -0
- samples/unet1b_384x640_0.jpg +3 -0
- samples/unet1b_416x640_0.jpg +3 -0
- samples/unet1b_448x640_0.jpg +3 -0
- samples/unet1b_480x640_0.jpg +3 -0
- samples/unet1b_512x640_0.jpg +3 -0
- samples/unet1b_544x640_0.jpg +3 -0
- samples/unet1b_576x640_0.jpg +3 -0
- samples/unet1b_608x640_0.jpg +3 -0
- samples/unet1b_640x320_0.jpg +3 -0
- samples/unet1b_640x352_0.jpg +3 -0
- samples/unet1b_640x384_0.jpg +3 -0
- samples/unet1b_640x416_0.jpg +3 -0
- samples/unet1b_640x448_0.jpg +3 -0
- samples/unet1b_640x480_0.jpg +3 -0
- samples/unet1b_640x512_0.jpg +3 -0
- samples/unet1b_640x544_0.jpg +3 -0
- samples/unet1b_640x576_0.jpg +3 -0
- samples/unet1b_640x608_0.jpg +3 -0
- samples/unet1b_640x640_0.jpg +3 -0
- samples/unet_320x640_0.jpg +2 -2
- samples/unet_352x640_0.jpg +2 -2
- samples/unet_384x640_0.jpg +2 -2
- samples/unet_416x640_0.jpg +2 -2
- samples/unet_448x640_0.jpg +2 -2
- samples/unet_480x640_0.jpg +2 -2
- samples/unet_512x640_0.jpg +2 -2
- samples/unet_544x640_0.jpg +2 -2
- samples/unet_576x640_0.jpg +2 -2
- samples/unet_608x640_0.jpg +2 -2
- samples/unet_640x320_0.jpg +2 -2
- samples/unet_640x352_0.jpg +2 -2
- samples/unet_640x384_0.jpg +2 -2
- samples/unet_640x416_0.jpg +2 -2
- samples/unet_640x448_0.jpg +2 -2
- samples/unet_640x480_0.jpg +2 -2
- samples/unet_640x512_0.jpg +2 -2
- samples/unet_640x544_0.jpg +2 -2
- samples/unet_640x576_0.jpg +2 -2
- samples/unet_640x608_0.jpg +2 -2
- samples/unet_640x640_0.jpg +2 -2
- train.py +19 -18
- unet/diffusion_pytorch_model.safetensors +2 -2
- unet1b.ipynb +3 -0
pipeline_sdxs-Copy1.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
def encode_prompt(self, prompt, negative_prompt, device, dtype):
|
| 28 |
+
"""
|
| 29 |
+
Полное соответствие функции encode_texts и get_negative_embedding из трейна.
|
| 30 |
+
"""
|
| 31 |
+
def get_single_encode(texts, is_negative=False):
|
| 32 |
+
if texts is None or texts == "":
|
| 33 |
+
# Логика get_negative_embedding из трейна
|
| 34 |
+
hidden_dim = self.text_encoder.config.hidden_size
|
| 35 |
+
|
| 36 |
+
shape = (1, self.text_encoder.config.max_position_embeddings, hidden_dim)
|
| 37 |
+
# В трейне для негатива: zeros для эмбеддингов и ones для маски
|
| 38 |
+
emb = torch.zeros(shape, dtype=dtype, device=device)
|
| 39 |
+
mask = torch.ones((1, self.text_encoder.config.max_position_embeddings), dtype=torch.int64, device=device)
|
| 40 |
+
return emb, mask
|
| 41 |
+
|
| 42 |
+
if isinstance(texts, str):
|
| 43 |
+
texts = [texts]
|
| 44 |
+
|
| 45 |
+
with torch.no_grad():
|
| 46 |
+
toks = self.tokenizer(
|
| 47 |
+
texts,
|
| 48 |
+
padding="max_length",
|
| 49 |
+
max_length=self.text_encoder.config.max_position_embeddings,
|
| 50 |
+
truncation=True,
|
| 51 |
+
return_tensors="pt"
|
| 52 |
+
).to(device)
|
| 53 |
+
|
| 54 |
+
outputs = self.text_encoder(
|
| 55 |
+
input_ids=toks.input_ids,
|
| 56 |
+
attention_mask=toks.attention_mask,
|
| 57 |
+
output_hidden_states=True
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
# 1. Выбираем нужный слой.
|
| 61 |
+
# -1 — это последний блок трансформера
|
| 62 |
+
# -2 — это предпоследний (стандарт для большинства современных моделей)
|
| 63 |
+
layer_index = -2
|
| 64 |
+
prompt_embeds = outputs.hidden_states[layer_index]
|
| 65 |
+
|
| 66 |
+
# 2. ДОБАВЛЯЕМ ФИНАЛЬНУЮ НОРМАЛИЗАЦИЮ
|
| 67 |
+
# В CLIP после всех блоков стоит слой LayerNorm.
|
| 68 |
+
# Если мы берем скрытые состояния напрямую, мы "проскакиваем" его.
|
| 69 |
+
# Нужно применить его вручную:
|
| 70 |
+
final_layer_norm = self.text_encoder.text_model.final_layer_norm
|
| 71 |
+
prompt_embeds = final_layer_norm(prompt_embeds)
|
| 72 |
+
|
| 73 |
+
return prompt_embeds, toks.attention_mask
|
| 74 |
+
|
| 75 |
+
# Получаем эмбеддинги
|
| 76 |
+
pos_embeds, pos_mask = get_single_encode(prompt)
|
| 77 |
+
neg_embeds, neg_mask = get_single_encode(negative_prompt, is_negative=True)
|
| 78 |
+
|
| 79 |
+
# Выравнивание батча
|
| 80 |
+
batch_size = pos_embeds.shape[0]
|
| 81 |
+
if neg_embeds.shape[0] != batch_size:
|
| 82 |
+
neg_embeds = neg_embeds.repeat(batch_size, 1, 1)
|
| 83 |
+
neg_mask = neg_mask.repeat(batch_size, 1)
|
| 84 |
+
|
| 85 |
+
# Конкатенация для CFG: [Negative, Positive]
|
| 86 |
+
text_embeddings = torch.cat([neg_embeds, pos_embeds], dim=0)
|
| 87 |
+
final_mask = torch.cat([neg_mask, pos_mask], dim=0)
|
| 88 |
+
|
| 89 |
+
return text_embeddings.to(dtype=dtype), final_mask.to(dtype=torch.int64)
|
| 90 |
+
|
| 91 |
+
@torch.no_grad()
|
| 92 |
+
def __call__(
|
| 93 |
+
self,
|
| 94 |
+
prompt: Union[str, List[str]],
|
| 95 |
+
negative_prompt: Optional[Union[str, List[str]]] = None,
|
| 96 |
+
height: int = 1024,
|
| 97 |
+
width: int = 1024,
|
| 98 |
+
num_inference_steps: int = 40, # Как в трейне n_diffusion_steps
|
| 99 |
+
guidance_scale: float = 4.0, # Как в трейне
|
| 100 |
+
generator: Optional[torch.Generator] = None,
|
| 101 |
+
output_type: str = "pil",
|
| 102 |
+
return_dict: bool = True,
|
| 103 |
+
**kwargs,
|
| 104 |
+
):
|
| 105 |
+
device = self.device
|
| 106 |
+
self.vae.to(device)
|
| 107 |
+
vae_scaling_factor = getattr(self.vae.config, "scaling_factor", 1.0)
|
| 108 |
+
vae_shift_factor = getattr(self.vae.config, "shift_factor", 0.0)
|
| 109 |
+
|
| 110 |
+
# 1. Encode Prompt
|
| 111 |
+
dtype = self.text_encoder.dtype
|
| 112 |
+
text_embeddings, attention_mask = self.encode_prompt(
|
| 113 |
+
prompt, negative_prompt, device, dtype
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
# 2. Prepare Latents
|
| 117 |
+
batch_size = 1 if isinstance(prompt, str) else len(prompt)
|
| 118 |
+
latent_channels = self.unet.config.in_channels
|
| 119 |
+
|
| 120 |
+
latents = torch.randn(
|
| 121 |
+
(batch_size, latent_channels, height // self.vae_scale_factor, width // self.vae_scale_factor),
|
| 122 |
+
generator=generator,
|
| 123 |
+
device=device,
|
| 124 |
+
dtype=dtype
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
# 3. Настройка Flow Matching шедулера
|
| 128 |
+
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
| 129 |
+
timesteps = self.scheduler.timesteps
|
| 130 |
+
|
| 131 |
+
# 4. Denoising Loop
|
| 132 |
+
for t in tqdm(timesteps, desc="Sampling"):
|
| 133 |
+
# CFG input
|
| 134 |
+
latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1 else latents
|
| 135 |
+
|
| 136 |
+
# Flow Matching обычно не требует scale_model_input,
|
| 137 |
+
# но оставим для совместимости с интерфейсом шедулера
|
| 138 |
+
if hasattr(self.scheduler, "scale_model_input"):
|
| 139 |
+
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
|
| 140 |
+
|
| 141 |
+
# Predict
|
| 142 |
+
model_out = self.unet(
|
| 143 |
+
latent_model_input,
|
| 144 |
+
t,
|
| 145 |
+
encoder_hidden_states=text_embeddings,
|
| 146 |
+
encoder_attention_mask=attention_mask,
|
| 147 |
+
return_dict=False,
|
| 148 |
+
)[0]
|
| 149 |
+
|
| 150 |
+
# CFG Logic
|
| 151 |
+
if guidance_scale > 1:
|
| 152 |
+
flow_uncond, flow_cond = model_out.chunk(2)
|
| 153 |
+
model_out = flow_uncond + guidance_scale * (flow_cond - flow_uncond)
|
| 154 |
+
|
| 155 |
+
# Step (Flow Matching Euler)
|
| 156 |
+
latents = self.scheduler.step(model_out, t, latents, return_dict=False)[0]
|
| 157 |
+
|
| 158 |
+
# 5. Decode
|
| 159 |
+
if output_type == "latent":
|
| 160 |
+
return SdxsPipelineOutput(images=latents)
|
| 161 |
+
|
| 162 |
+
latents = latents * vae_scaling_factor + vae_shift_factor
|
| 163 |
+
image = self.vae.decode(latents.to(self.vae.dtype), return_dict=False)[0]
|
| 164 |
+
|
| 165 |
+
# Пост-процессинг
|
| 166 |
+
image = (image / 2 + 0.5).clamp(0, 1)
|
| 167 |
+
image = image.cpu().permute(0, 2, 3, 1).float().numpy()
|
| 168 |
+
|
| 169 |
+
if output_type == "pil":
|
| 170 |
+
image = (image * 255).round().astype("uint8")
|
| 171 |
+
image = [Image.fromarray(img) for img in image]
|
| 172 |
+
|
| 173 |
+
if not return_dict:
|
| 174 |
+
return image
|
| 175 |
+
|
| 176 |
+
return SdxsPipelineOutput(images=image)
|
pipeline_sdxs.py
CHANGED
|
@@ -24,17 +24,31 @@ class SdxsPipeline(DiffusionPipeline):
|
|
| 24 |
)
|
| 25 |
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
def encode_prompt(self, prompt, negative_prompt, device, dtype):
|
| 28 |
-
"""
|
| 29 |
-
Полное соответствие функции encode_texts и get_negative_embedding из трейна.
|
| 30 |
-
"""
|
| 31 |
def get_single_encode(texts, is_negative=False):
|
| 32 |
if texts is None or texts == "":
|
| 33 |
-
# Логика get_negative_embedding из трейна
|
| 34 |
hidden_dim = self.text_encoder.config.hidden_size
|
| 35 |
-
|
| 36 |
shape = (1, self.text_encoder.config.max_position_embeddings, hidden_dim)
|
| 37 |
-
# В трейне для негатива: zeros для эмбеддингов и ones для маски
|
| 38 |
emb = torch.zeros(shape, dtype=dtype, device=device)
|
| 39 |
mask = torch.ones((1, self.text_encoder.config.max_position_embeddings), dtype=torch.int64, device=device)
|
| 40 |
return emb, mask
|
|
@@ -57,32 +71,21 @@ class SdxsPipeline(DiffusionPipeline):
|
|
| 57 |
output_hidden_states=True
|
| 58 |
)
|
| 59 |
|
| 60 |
-
# 1. Выбираем нужный слой.
|
| 61 |
-
# -1 — это последний блок трансформера
|
| 62 |
-
# -2 — это предпоследний (стандарт для большинства современных моделей)
|
| 63 |
layer_index = -2
|
| 64 |
prompt_embeds = outputs.hidden_states[layer_index]
|
| 65 |
-
|
| 66 |
-
# 2. ДОБАВЛЯЕМ ФИНАЛЬНУЮ НОРМАЛИЗАЦИЮ
|
| 67 |
-
# В CLIP после всех блоков стоит слой LayerNorm.
|
| 68 |
-
# Если мы берем скрытые состояния напрямую, мы "проскакиваем" его.
|
| 69 |
-
# Нужно применить его вручную:
|
| 70 |
final_layer_norm = self.text_encoder.text_model.final_layer_norm
|
| 71 |
prompt_embeds = final_layer_norm(prompt_embeds)
|
| 72 |
|
| 73 |
return prompt_embeds, toks.attention_mask
|
| 74 |
|
| 75 |
-
# Получаем эмбеддинги
|
| 76 |
pos_embeds, pos_mask = get_single_encode(prompt)
|
| 77 |
neg_embeds, neg_mask = get_single_encode(negative_prompt, is_negative=True)
|
| 78 |
|
| 79 |
-
# Выравнивание батча
|
| 80 |
batch_size = pos_embeds.shape[0]
|
| 81 |
if neg_embeds.shape[0] != batch_size:
|
| 82 |
neg_embeds = neg_embeds.repeat(batch_size, 1, 1)
|
| 83 |
neg_mask = neg_mask.repeat(batch_size, 1)
|
| 84 |
|
| 85 |
-
# Конкатенация для CFG: [Negative, Positive]
|
| 86 |
text_embeddings = torch.cat([neg_embeds, pos_embeds], dim=0)
|
| 87 |
final_mask = torch.cat([neg_mask, pos_mask], dim=0)
|
| 88 |
|
|
@@ -92,11 +95,13 @@ class SdxsPipeline(DiffusionPipeline):
|
|
| 92 |
def __call__(
|
| 93 |
self,
|
| 94 |
prompt: Union[str, List[str]],
|
|
|
|
|
|
|
| 95 |
negative_prompt: Optional[Union[str, List[str]]] = None,
|
| 96 |
height: int = 1024,
|
| 97 |
width: int = 1024,
|
| 98 |
-
num_inference_steps: int = 40,
|
| 99 |
-
guidance_scale: float = 4.0,
|
| 100 |
generator: Optional[torch.Generator] = None,
|
| 101 |
output_type: str = "pil",
|
| 102 |
return_dict: bool = True,
|
|
@@ -113,28 +118,53 @@ class SdxsPipeline(DiffusionPipeline):
|
|
| 113 |
prompt, negative_prompt, device, dtype
|
| 114 |
)
|
| 115 |
|
| 116 |
-
# 2.
|
| 117 |
batch_size = 1 if isinstance(prompt, str) else len(prompt)
|
| 118 |
latent_channels = self.unet.config.in_channels
|
| 119 |
-
|
| 120 |
-
latents = torch.randn(
|
| 121 |
-
(batch_size, latent_channels, height // self.vae_scale_factor, width // self.vae_scale_factor),
|
| 122 |
-
generator=generator,
|
| 123 |
-
device=device,
|
| 124 |
-
dtype=dtype
|
| 125 |
-
)
|
| 126 |
|
| 127 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
| 129 |
timesteps = self.scheduler.timesteps
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
# 4. Denoising Loop
|
| 132 |
for t in tqdm(timesteps, desc="Sampling"):
|
| 133 |
-
# CFG input
|
| 134 |
latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1 else latents
|
| 135 |
|
| 136 |
-
# Flow Matching обычно не требует scale_model_input,
|
| 137 |
-
# но оставим для совместимости с интерфейсом шедулера
|
| 138 |
if hasattr(self.scheduler, "scale_model_input"):
|
| 139 |
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
|
| 140 |
|
|
@@ -152,7 +182,7 @@ class SdxsPipeline(DiffusionPipeline):
|
|
| 152 |
flow_uncond, flow_cond = model_out.chunk(2)
|
| 153 |
model_out = flow_uncond + guidance_scale * (flow_cond - flow_uncond)
|
| 154 |
|
| 155 |
-
# Step
|
| 156 |
latents = self.scheduler.step(model_out, t, latents, return_dict=False)[0]
|
| 157 |
|
| 158 |
# 5. Decode
|
|
|
|
| 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 |
+
w, h = image.size
|
| 31 |
+
aspect_ratio = width / height
|
| 32 |
+
if w / h > aspect_ratio:
|
| 33 |
+
new_w = int(h * aspect_ratio)
|
| 34 |
+
left = (w - new_w) // 2
|
| 35 |
+
image = image.crop((left, 0, left + new_w, h))
|
| 36 |
+
else:
|
| 37 |
+
new_h = int(w / aspect_ratio)
|
| 38 |
+
top = (h - new_h) // 2
|
| 39 |
+
image = image.crop((0, top, w, top + new_h))
|
| 40 |
+
|
| 41 |
+
image = image.resize((width, height), resample=Image.LANCZOS)
|
| 42 |
+
image = np.array(image).astype(np.float32) / 255.0
|
| 43 |
+
image = image[None].transpose(0, 3, 1, 2) # [1, C, H, W]
|
| 44 |
+
image = torch.from_numpy(image)
|
| 45 |
+
return 2.0 * image - 1.0 # В диапазон [-1, 1]
|
| 46 |
+
|
| 47 |
def encode_prompt(self, prompt, negative_prompt, device, dtype):
|
|
|
|
|
|
|
|
|
|
| 48 |
def get_single_encode(texts, is_negative=False):
|
| 49 |
if texts is None or texts == "":
|
|
|
|
| 50 |
hidden_dim = self.text_encoder.config.hidden_size
|
|
|
|
| 51 |
shape = (1, self.text_encoder.config.max_position_embeddings, hidden_dim)
|
|
|
|
| 52 |
emb = torch.zeros(shape, dtype=dtype, device=device)
|
| 53 |
mask = torch.ones((1, self.text_encoder.config.max_position_embeddings), dtype=torch.int64, device=device)
|
| 54 |
return emb, mask
|
|
|
|
| 71 |
output_hidden_states=True
|
| 72 |
)
|
| 73 |
|
|
|
|
|
|
|
|
|
|
| 74 |
layer_index = -2
|
| 75 |
prompt_embeds = outputs.hidden_states[layer_index]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
final_layer_norm = self.text_encoder.text_model.final_layer_norm
|
| 77 |
prompt_embeds = final_layer_norm(prompt_embeds)
|
| 78 |
|
| 79 |
return prompt_embeds, toks.attention_mask
|
| 80 |
|
|
|
|
| 81 |
pos_embeds, pos_mask = get_single_encode(prompt)
|
| 82 |
neg_embeds, neg_mask = get_single_encode(negative_prompt, is_negative=True)
|
| 83 |
|
|
|
|
| 84 |
batch_size = pos_embeds.shape[0]
|
| 85 |
if neg_embeds.shape[0] != batch_size:
|
| 86 |
neg_embeds = neg_embeds.repeat(batch_size, 1, 1)
|
| 87 |
neg_mask = neg_mask.repeat(batch_size, 1)
|
| 88 |
|
|
|
|
| 89 |
text_embeddings = torch.cat([neg_embeds, pos_embeds], dim=0)
|
| 90 |
final_mask = torch.cat([neg_mask, pos_mask], dim=0)
|
| 91 |
|
|
|
|
| 95 |
def __call__(
|
| 96 |
self,
|
| 97 |
prompt: Union[str, List[str]],
|
| 98 |
+
image: Optional[Union[Image.Image, List[Image.Image]]] = None, # Добавлен параметр изображения
|
| 99 |
+
coef: float = 0.5, # Коэффициент влияния (strength): 1.0 - полный шум, 0.0 - оригинал
|
| 100 |
negative_prompt: Optional[Union[str, List[str]]] = None,
|
| 101 |
height: int = 1024,
|
| 102 |
width: int = 1024,
|
| 103 |
+
num_inference_steps: int = 40,
|
| 104 |
+
guidance_scale: float = 4.0,
|
| 105 |
generator: Optional[torch.Generator] = None,
|
| 106 |
output_type: str = "pil",
|
| 107 |
return_dict: bool = True,
|
|
|
|
| 118 |
prompt, negative_prompt, device, dtype
|
| 119 |
)
|
| 120 |
|
| 121 |
+
# 2. Определение размеров и батча
|
| 122 |
batch_size = 1 if isinstance(prompt, str) else len(prompt)
|
| 123 |
latent_channels = self.unet.config.in_channels
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
+
# --- ЛОГИКА ПОДГОТОВКИ ЛАТЕНТОВ (TXT2IMG vs IMG2IMG) ---
|
| 126 |
+
if image is not None:
|
| 127 |
+
# Если передана одна картинка на батч текстов - размножаем её
|
| 128 |
+
if isinstance(image, Image.Image):
|
| 129 |
+
image = [image] * batch_size
|
| 130 |
+
|
| 131 |
+
# Обработка картинок: ресайз/кроп и перевод в тензоры
|
| 132 |
+
image_tensors = torch.cat([self.preprocess_image(img, width, height) for img in image]).to(device=device, dtype=vae_dtype := self.vae.dtype)
|
| 133 |
+
|
| 134 |
+
# Кодируем в латенты
|
| 135 |
+
init_latents = self.vae.encode(image_tensors).latent_dist.sample(generator=generator)
|
| 136 |
+
init_latents = (init_latents - vae_shift_factor) / vae_scaling_factor
|
| 137 |
+
init_latents = init_latents.to(dtype=dtype)
|
| 138 |
+
|
| 139 |
+
# Создаем шум
|
| 140 |
+
noise = torch.randn(init_latents.shape, generator=generator, device=device, dtype=dtype)
|
| 141 |
+
|
| 142 |
+
# Flow Matching шум: x_t = (1 - t)*x0 + t*epsilon
|
| 143 |
+
# Здесь t = coef. Если coef=1.0, получаем чистый шум.
|
| 144 |
+
latents = (1.0 - coef) * init_latents + coef * noise
|
| 145 |
+
else:
|
| 146 |
+
# Стандартный txt2img (чистый шум)
|
| 147 |
+
latents = torch.randn(
|
| 148 |
+
(batch_size, latent_channels, height // self.vae_scale_factor, width // self.vae_scale_factor),
|
| 149 |
+
generator=generator,
|
| 150 |
+
device=device,
|
| 151 |
+
dtype=dtype
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
# 3. Настройка таймстепов
|
| 155 |
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
| 156 |
timesteps = self.scheduler.timesteps
|
| 157 |
|
| 158 |
+
# --- ОБРЕЗКА ТАЙМСТЕПОВ ДЛЯ IMG2IMG ---
|
| 159 |
+
if image is not None:
|
| 160 |
+
# Оставляем только те шаги, которые соответствуют уровню шума coef
|
| 161 |
+
# В Flow Matching t идет от 1.0 к 0.0. Нам нужны шаги <= coef.
|
| 162 |
+
timesteps = timesteps[timesteps <= coef]
|
| 163 |
+
|
| 164 |
# 4. Denoising Loop
|
| 165 |
for t in tqdm(timesteps, desc="Sampling"):
|
|
|
|
| 166 |
latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1 else latents
|
| 167 |
|
|
|
|
|
|
|
| 168 |
if hasattr(self.scheduler, "scale_model_input"):
|
| 169 |
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
|
| 170 |
|
|
|
|
| 182 |
flow_uncond, flow_cond = model_out.chunk(2)
|
| 183 |
model_out = flow_uncond + guidance_scale * (flow_cond - flow_uncond)
|
| 184 |
|
| 185 |
+
# Step
|
| 186 |
latents = self.scheduler.step(model_out, t, latents, return_dict=False)[0]
|
| 187 |
|
| 188 |
# 5. Decode
|
samples/unet1b_320x640_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_352x640_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_384x640_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_416x640_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_448x640_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_480x640_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_512x640_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_544x640_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_576x640_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_608x640_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_640x320_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_640x352_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_640x384_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_640x416_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_640x448_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_640x480_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_640x512_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_640x544_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_640x576_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_640x608_0.jpg
ADDED
|
Git LFS Details
|
samples/unet1b_640x640_0.jpg
ADDED
|
Git LFS Details
|
samples/unet_320x640_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_352x640_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_384x640_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_416x640_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_448x640_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_480x640_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_512x640_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_544x640_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_576x640_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_608x640_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_640x320_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_640x352_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_640x384_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_640x416_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_640x448_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_640x480_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_640x512_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_640x544_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_640x576_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_640x608_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
samples/unet_640x640_0.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
train.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
| 1 |
#from comet_ml import Experiment
|
| 2 |
import os
|
|
|
|
|
|
|
|
|
|
| 3 |
import math
|
| 4 |
import torch
|
| 5 |
import numpy as np
|
|
@@ -9,7 +12,7 @@ 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,FlowMatchEulerDiscreteScheduler
|
| 12 |
-
from accelerate import Accelerator
|
| 13 |
from datasets import load_from_disk
|
| 14 |
from tqdm import tqdm
|
| 15 |
from PIL import Image, ImageOps
|
|
@@ -29,11 +32,12 @@ from transformers import AutoTokenizer, AutoModel
|
|
| 29 |
# --------------------------- Параметры ---------------------------
|
| 30 |
ds_path = "/workspace/sdxs-08b/datasets/d123_640_sd15"
|
| 31 |
project = "unet"
|
| 32 |
-
|
|
|
|
| 33 |
base_learning_rate = 2e-6
|
| 34 |
-
min_learning_rate =
|
| 35 |
-
num_epochs =
|
| 36 |
-
sample_interval_share =
|
| 37 |
cfg_dropout = 0.10
|
| 38 |
max_length = 248
|
| 39 |
use_wandb = True
|
|
@@ -55,7 +59,6 @@ torch.backends.cudnn.allow_tf32 = True
|
|
| 55 |
torch.backends.cuda.enable_flash_sdp(True)
|
| 56 |
torch.backends.cuda.enable_mem_efficient_sdp(True)
|
| 57 |
torch.backends.cuda.enable_math_sdp(False) # Отключаем медленный вариант
|
| 58 |
-
dtype = torch.float32
|
| 59 |
save_barrier = 1.05
|
| 60 |
warmup_percent = 0.03
|
| 61 |
percentile_clipping = 95
|
|
@@ -64,17 +67,9 @@ eps = 1e-7
|
|
| 64 |
clip_grad_norm = 1.0
|
| 65 |
limit = 0
|
| 66 |
checkpoints_folder = ""
|
| 67 |
-
mixed_precision = "no"
|
| 68 |
gradient_accumulation_steps = 1
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True,max_split_size_mb:128"
|
| 72 |
-
|
| 73 |
-
accelerator = Accelerator(
|
| 74 |
-
mixed_precision=mixed_precision,
|
| 75 |
-
gradient_accumulation_steps=gradient_accumulation_steps
|
| 76 |
-
)
|
| 77 |
-
device = accelerator.device
|
| 78 |
|
| 79 |
# Параметры для диффузии
|
| 80 |
n_diffusion_steps = 40
|
|
@@ -95,6 +90,12 @@ if fixed_seed:
|
|
| 95 |
if torch.cuda.is_available():
|
| 96 |
torch.cuda.manual_seed_all(seed)
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
print("init")
|
| 99 |
|
| 100 |
# --------------------------- Инициализация WandB ---------------------------
|
|
@@ -128,7 +129,7 @@ tokenizer = AutoTokenizer.from_pretrained("tokenizer")
|
|
| 128 |
text_model = AutoModel.from_pretrained("text_encoder").to(device).eval()
|
| 129 |
scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained("scheduler")
|
| 130 |
|
| 131 |
-
def encode_texts(texts, max_length=max_length):
|
| 132 |
if texts is None:
|
| 133 |
texts = [""]
|
| 134 |
|
|
@@ -645,7 +646,7 @@ for epoch in range(start_epoch, start_epoch + num_epochs):
|
|
| 645 |
if not fbp:
|
| 646 |
if accelerator.sync_gradients:
|
| 647 |
grad_val = accelerator.clip_grad_norm_(unet.parameters(), clip_grad_norm)
|
| 648 |
-
grad = float(grad_val)
|
| 649 |
optimizer.step()
|
| 650 |
lr_scheduler.step()
|
| 651 |
optimizer.zero_grad(set_to_none=True)
|
|
|
|
| 1 |
#from comet_ml import Experiment
|
| 2 |
import os
|
| 3 |
+
os.environ["NCCL_P2P_DISABLE"] = "1"
|
| 4 |
+
os.environ["NCCL_IB_DISABLE"] = "1"
|
| 5 |
+
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
|
| 6 |
import math
|
| 7 |
import torch
|
| 8 |
import numpy as np
|
|
|
|
| 12 |
from torch.optim.lr_scheduler import LambdaLR
|
| 13 |
from collections import defaultdict
|
| 14 |
from diffusers import UNet2DConditionModel, AutoencoderKL,AutoencoderKLFlux2,AsymmetricAutoencoderKL,FlowMatchEulerDiscreteScheduler
|
| 15 |
+
from accelerate import Accelerator, DeepSpeedPlugin
|
| 16 |
from datasets import load_from_disk
|
| 17 |
from tqdm import tqdm
|
| 18 |
from PIL import Image, ImageOps
|
|
|
|
| 32 |
# --------------------------- Параметры ---------------------------
|
| 33 |
ds_path = "/workspace/sdxs-08b/datasets/d123_640_sd15"
|
| 34 |
project = "unet"
|
| 35 |
+
## total batch (split // num `GPU)
|
| 36 |
+
batch_size = 20
|
| 37 |
base_learning_rate = 2e-6
|
| 38 |
+
min_learning_rate = 7e-7
|
| 39 |
+
num_epochs = 2
|
| 40 |
+
sample_interval_share = 10
|
| 41 |
cfg_dropout = 0.10
|
| 42 |
max_length = 248
|
| 43 |
use_wandb = True
|
|
|
|
| 59 |
torch.backends.cuda.enable_flash_sdp(True)
|
| 60 |
torch.backends.cuda.enable_mem_efficient_sdp(True)
|
| 61 |
torch.backends.cuda.enable_math_sdp(False) # Отключаем медленный вариант
|
|
|
|
| 62 |
save_barrier = 1.05
|
| 63 |
warmup_percent = 0.03
|
| 64 |
percentile_clipping = 95
|
|
|
|
| 67 |
clip_grad_norm = 1.0
|
| 68 |
limit = 0
|
| 69 |
checkpoints_folder = ""
|
|
|
|
| 70 |
gradient_accumulation_steps = 1
|
| 71 |
+
dtype = torch.float32
|
| 72 |
+
mixed_precision = "no"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
# Параметры для диффузии
|
| 75 |
n_diffusion_steps = 40
|
|
|
|
| 90 |
if torch.cuda.is_available():
|
| 91 |
torch.cuda.manual_seed_all(seed)
|
| 92 |
|
| 93 |
+
accelerator = Accelerator(
|
| 94 |
+
mixed_precision=mixed_precision,
|
| 95 |
+
gradient_accumulation_steps=gradient_accumulation_steps
|
| 96 |
+
)
|
| 97 |
+
device = accelerator.device
|
| 98 |
+
|
| 99 |
print("init")
|
| 100 |
|
| 101 |
# --------------------------- Инициализация WandB ---------------------------
|
|
|
|
| 129 |
text_model = AutoModel.from_pretrained("text_encoder").to(device).eval()
|
| 130 |
scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained("scheduler")
|
| 131 |
|
| 132 |
+
def encode_texts(texts, max_length=max_length):
|
| 133 |
if texts is None:
|
| 134 |
texts = [""]
|
| 135 |
|
|
|
|
| 646 |
if not fbp:
|
| 647 |
if accelerator.sync_gradients:
|
| 648 |
grad_val = accelerator.clip_grad_norm_(unet.parameters(), clip_grad_norm)
|
| 649 |
+
grad = grad_val.float().item() if torch.is_tensor(grad_val) else float(grad_val)
|
| 650 |
optimizer.step()
|
| 651 |
lr_scheduler.step()
|
| 652 |
optimizer.zero_grad(set_to_none=True)
|
unet/diffusion_pytorch_model.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:866fefbc206ff908dbc2fd5da4bcc21215fed0e3470c16b494eb4fb3d5810804
|
| 3 |
+
size 3438444088
|
unet1b.ipynb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:37f536648a3fac58f603ac638ce41c79533c9e9f0da2fd10b6dc1bf8e35470e1
|
| 3 |
+
size 64797
|