Multimodal Large Language Model Support?
ifsheldon opened this issue ยท 21 comments
Hi! Great work!
Have you tried leveraging MLLM to be the prompt encoder? We have open-source MLLM now, and I think this will be an easy extension but very powerful one. For example, we could give image prompts without ControlNet or other mechanisms to inject image information. We just tell MLLM what we want with text and images, then SD generates it for us.
Update: I see this in Conclusion and Limitation. If you can release training code, then probably the community can also try to approach this direction and to adapt various LLMs
that is a good idea!
we add some early results in https://github.com/TencentQQGYLab/ELLA?tab=readme-ov-file#-emma---efficient-multi-modal-adapter-work-in-progress
Thanks! Awesome! These early results seem to have IPAdpater-like capabilities. Probably it also has strong in-context learning capability like given a pair of (original image, target image) as an example, it can learn and modify another image.
Hi! Great work!
Have you tried leveraging MLLM to be the prompt encoder? We have open-source MLLM now, and I think this will be an easy extension but very powerful one. For example, we could give image prompts without ControlNet or other mechanisms to inject image information. We just tell MLLM what we want with text and images, then SD generates it for us.
Update: I see this in Conclusion and Limitation. If you can release training code, then probably the community can also try to approach this direction and to adapt various LLMs
Hello, may I ask where this part of open source work can be seen? What is the paper?
we add some early results in https://github.com/TencentQQGYLab/ELLA?tab=readme-ov-file#-emma---efficient-multi-modal-adapter-work-in-progress
I reproduced a version of Ella based on SD XL, and the effect is really improved over the original SD XL. I have read your work on multimodal integration, can you briefly describe your approach?
I reproduced a version of Ella based on SD XL, and the effect is really improved over the original SD XL.
wow! Can you show some comparisons between the ELLA-SDXL results you reproduced and the original SDXL results?
can you briefly describe your approach?
EMMA is actually using both text and image embeddings as input for the Connector. The current method is still quite simple and not sufficient to write a 8-page paper, so we are conducting more experiments.
I reproduced a version of Ella based on SD XL, and the effect is really improved over the original SD XL.
wow! Can you show some comparisons between the ELLA-SDXL results you reproduced and the original SDXL results?
can you briefly describe your approach?
EMMA is actually using both text and image embeddings as input for the Connector. The current method is still quite simple and not sufficient to write a 8-page paper, so we are conducting more experiments.
We have improved the basic Ella structure and used Geneval for model evaluation. According to the result of repetition, the improvement in the Two objects and Color attribution is significant. Is it consistent with your conclusion? In addition, according to your description of EMMA, it feels similar to the idea of M2Chat, maybe we can maintain frequent communication in the future work
I reproduced a version of Ella based on SD XL, and the effect is really improved over the original SD XL. I have read your work on multimodal integration, can you briefly describe your approach?
@plastic0313 Can you please share your training scripts? I think it will really unlocks a lot of space for the community to explore. Say taking advantage of LLaMa 3
According to the result of repetition, the improvement in the Two objects and Color attribution is significant. Is it consistent with your conclusion? In addition, according to your description of EMMA, it feels similar to the idea of M2Chat,
This depends on the data you use. Generally speaking, VLM-annotated captions contain a lot of accurate color descriptions, so the performance on color is much better.
maybe we can maintain frequent communication in the future work
Of course, you can contact me through the contact information on my personal website.
Are there any version of EMMA (even on beta) that I can we can play with? Would love to learn more about some preliminary results on the image generation.
we add some early results in https://github.com/TencentQQGYLab/ELLA?tab=readme-ov-file#-emma---efficient-multi-modal-adapter-work-in-progress
I reproduced a version of Ella based on SD XL, and the effect is really improved over the original SD XL. I have read your work on multimodal integration, can you briefly describe your approach?
Any chance you could share your work @plastic0313 ?
I would like to have a test drive :)
Sincerely, best regards.
maybe i'm the only one, but the lack of weights or experiments or training logs really make the SDXL claims hard to believe.
it seems like most people feel the weights don't actually exist, nor do they behave the way they are claimed to in the paper. releasing the weights and training data can be of help to your project for this.
we add some early results in https://github.com/TencentQQGYLab/ELLA?tab=readme-ov-file#-emma---efficient-multi-modal-adapter-work-in-progress
I reproduced a version of Ella based on SD XL, and the effect is really improved over the original SD XL. I have read your work on multimodal integration, can you briefly describe your approach?
Could you mind sharing what kind of datasets you used? 34M dataset is really challenging for me.
i am not sure they actually even did what they claim.. we have been trying to train it for ~2 months. it just doesnt work for sdxl since it has two text encoders.
i am not sure they actually even did what they claim.. we have been trying to train it for ~2 months. it just doesnt work for sdxl since it has two text encoders.
The author said they used attention pooling to transform Ella embedding for fitting pooled embedding in SDXL. I have implemented this and it works. You can have a try.
just publish your results instead.
i am not sure they actually even did what they claim.. we have been trying to train it for ~2 months. it just doesnt work for sdxl since it has two text encoders.
The author said they used attention pooling to transform Ella embedding for fitting pooled embedding in SDXL. I have implemented this and it works. You can have a try.
@George0726 May you provide details or implementation, please? Is it self-attention pooling?
i dont think george is being honest about it. his issues opened on my project do not indicate a deep understanding of this type of work or the code to write
i dont think george is being honest about it. his issues opened on my project do not indicate a deep understanding of this type of work or the code to write
Why are you so rude to me? I just kindly ask a question about the bugs of your extended SD3 network.. I can share my ella code here.
from collections import OrderedDict
from typing import Optional
import torch
import torch.nn as nn
from diffusers.models.embeddings import TimestepEmbedding, Timesteps
from transformers import T5EncoderModel, T5Tokenizer
import torch
import torch.nn as nn
import torch.nn.functional as F
class AttentionPool2d(nn.Module):
def __init__(self, input_dim, output_dim, num_heads):
super(AttentionPool2d, self).__init__()
self.num_heads = num_heads
self.qkv = nn.Linear(input_dim, output_dim * 3)
self.proj = nn.Linear(output_dim, output_dim)
self.scale = (output_dim // num_heads) ** -0.5
def forward(self, x):
B, N, C = x.shape # Batch size, Sequence length, Channel size
qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, -1)
q, k, v = qkv.permute(2, 0, 3, 1, 4)
q = q * self.scale
attn = (q @ k.transpose(-2, -1)).softmax(dim=-1)
x = (attn @ v).transpose(1, 2).reshape(B, N, -1)
x = self.proj(x[:, 0])
return x
class AdaLayerNorm(nn.Module):
def __init__(self, embedding_dim: int, time_embedding_dim: Optional[int] = None):
super().__init__()
if time_embedding_dim is None:
time_embedding_dim = embedding_dim
self.silu = nn.SiLU()
self.linear = nn.Linear(time_embedding_dim, 2 * embedding_dim, bias=True)
nn.init.zeros_(self.linear.weight)
nn.init.zeros_(self.linear.bias)
self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False, eps=1e-6)
def forward(
self, x: torch.Tensor, timestep_embedding: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]:
emb = self.linear(self.silu(timestep_embedding))
shift, scale = emb.view(len(x), 1, -1).chunk(2, dim=-1)
x = self.norm(x) * (1 + scale) + shift
return x
class SquaredReLU(nn.Module):
def forward(self, x: torch.Tensor):
return torch.square(torch.relu(x))
class PerceiverAttentionBlock(nn.Module):
def __init__(
self, d_model: int, n_heads: int, time_embedding_dim: Optional[int] = None
):
super().__init__()
self.attn = nn.MultiheadAttention(d_model, n_heads, batch_first=True)
self.mlp = nn.Sequential(
OrderedDict(
[
("c_fc", nn.Linear(d_model, d_model * 4)),
("sq_relu", SquaredReLU()),
("c_proj", nn.Linear(d_model * 4, d_model)),
]
)
)
self.ln_1 = AdaLayerNorm(d_model, time_embedding_dim)
self.ln_2 = AdaLayerNorm(d_model, time_embedding_dim)
self.ln_ff = AdaLayerNorm(d_model, time_embedding_dim)
def attention(self, q: torch.Tensor, kv: torch.Tensor):
attn_output, attn_output_weights = self.attn(q, kv, kv, need_weights=False)
return attn_output
def forward(
self,
x: torch.Tensor,
latents: torch.Tensor,
timestep_embedding: torch.Tensor = None,
):
normed_latents = self.ln_1(latents, timestep_embedding)
latents = latents + self.attention(
q=normed_latents,
kv=torch.cat([normed_latents, self.ln_2(x, timestep_embedding)], dim=1),
)
latents = latents + self.mlp(self.ln_ff(latents, timestep_embedding))
return latents
class PerceiverResampler(nn.Module):
def __init__(
self,
width: int = 768,
layers: int = 6,
heads: int = 8,
num_latents: int = 64,
output_dim=None,
input_dim=None,
time_embedding_dim: Optional[int] = None,
):
super().__init__()
self.output_dim = output_dim
self.input_dim = input_dim
self.latents = nn.Parameter(width**-0.5 * torch.randn(num_latents, width))
self.time_aware_linear = nn.Linear(
time_embedding_dim or width, width, bias=True
)
if self.input_dim is not None:
self.proj_in = nn.Linear(input_dim, width)
self.perceiver_blocks = nn.Sequential(
*[
PerceiverAttentionBlock(
width, heads, time_embedding_dim=time_embedding_dim
)
for _ in range(layers)
]
)
if self.output_dim is not None:
self.proj_out = nn.Sequential(
nn.Linear(width, output_dim), nn.LayerNorm(output_dim)
)
def forward(self, x: torch.Tensor, timestep_embedding: torch.Tensor = None):
learnable_latents = self.latents.unsqueeze(dim=0).repeat(len(x), 1, 1)
latents = learnable_latents + self.time_aware_linear(
torch.nn.functional.silu(timestep_embedding)
)
if self.input_dim is not None:
x = self.proj_in(x)
for p_block in self.perceiver_blocks:
latents = p_block(x, latents, timestep_embedding=timestep_embedding)
if self.output_dim is not None:
latents = self.proj_out(latents)
return latents
class T5TextEmbedder(nn.Module):
def __init__(self, pretrained_path="google/flan-t5-xl", max_length=None):
super().__init__()
self.model = T5EncoderModel.from_pretrained(pretrained_path)
self.tokenizer = T5Tokenizer.from_pretrained(pretrained_path)
self.max_length = max_length
def forward(
self, caption, text_input_ids=None, attention_mask=None, max_length=None
):
if max_length is None:
max_length = self.max_length
if text_input_ids is None or attention_mask is None:
if max_length is not None:
text_inputs = self.tokenizer(
caption,
return_tensors="pt",
add_special_tokens=True,
max_length=max_length,
padding="max_length",
truncation=True,
)
else:
text_inputs = self.tokenizer(
caption, return_tensors="pt", add_special_tokens=True
)
text_input_ids = text_inputs.input_ids
attention_mask = text_inputs.attention_mask
text_input_ids = text_input_ids.to(self.model.device)
attention_mask = attention_mask.to(self.model.device)
outputs = self.model(text_input_ids, attention_mask=attention_mask)
embeddings = outputs.last_hidden_state
return embeddings
class ELLA(nn.Module):
def __init__(
self,
time_channel=320,
time_embed_dim=768,
act_fn: str = "silu",
out_dim: Optional[int] = None,
width=768,
layers=6,
heads=8,
num_latents=64,
input_dim=2048,
pooled_output_dim = 1280
):
super().__init__()
self.position = Timesteps(
time_channel, flip_sin_to_cos=True, downscale_freq_shift=0
)
self.time_embedding = TimestepEmbedding(
in_channels=time_channel,
time_embed_dim=time_embed_dim,
act_fn=act_fn,
out_dim=out_dim,
)
self.connector = PerceiverResampler(
width=width,
layers=layers,
heads=heads,
num_latents=num_latents,
input_dim=input_dim,
time_embedding_dim=time_embed_dim,
)
self.additional_pool = AttentionPool2d (
input_dim=input_dim,
output_dim=pooled_output_dim,
num_heads=heads
)
def forward(self, text_encode_features, timesteps):
device = text_encode_features.device
dtype = text_encode_features.dtype
ori_time_feature = self.position(timesteps.view(-1)).to(device, dtype=dtype)
ori_time_feature = (
ori_time_feature.unsqueeze(dim=1)
if ori_time_feature.ndim == 2
else ori_time_feature
)
ori_time_feature = ori_time_feature.expand(len(text_encode_features), -1, -1)
time_embedding = self.time_embedding(ori_time_feature)
encoder_hidden_states = self.connector(
text_encode_features, timestep_embedding=time_embedding
)
pooled_hidden_states_embeding = self.additional_pool(encoder_hidden_states)
return encoder_hidden_states, pooled_hidden_states_embeding
i dont think george is being honest about it. his issues opened on my project do not indicate a deep understanding of this type of work or the code to write
Maybe I am not as good as you. You are so rude to judge me on my integrity. I cannot get the results better than SDXL version. I am not it is about the size of the dataset I use or the ELLA itself. But at least I know using average pooling make it generate images ...
#!/usr/bin/env python
# coding=utf-8
# Copyright 2023 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
import argparse
import functools
import gc
import logging
import math
import os
import random
import shutil
from pathlib import Path
import json
import time
import cv2
os.environ["ACCELERATE_LOG_LEVEL"] = "WARNING"
# os.environ["SIMPLETUNER_LOG_LEVEL"] = "DEBUG"
import hashlib,glob
import accelerate
import numpy as np
import torch
import torch.nn.functional as F
import torch.utils.checkpoint
import transformers
from accelerate import Accelerator
from accelerate.logging import get_logger
from accelerate.utils import ProjectConfiguration, set_seed
from huggingface_hub import create_repo, upload_folder
from packaging import version
from PIL import Image, ImageFilter, ImageFile, ImageDraw
ImageFile.LOAD_TRUNCATED_IMAGES = True
from torchvision import transforms
from tqdm.auto import tqdm
from transformers import AutoTokenizer, PretrainedConfig
from torch.utils.data import Dataset, Sampler
import diffusers
from diffusers import (
AutoencoderKL,
ControlNetModel,
DDPMScheduler,
StableDiffusionXLControlNetPipeline,
StableDiffusionXLControlNetInpaintPipeline,
UNet2DConditionModel,
UniPCMultistepScheduler,
)
from diffusers.optimization import get_scheduler
from diffusers.utils import check_min_version, is_wandb_available, make_image_grid
from diffusers.utils.import_utils import is_xformers_available
from torchvision.transforms.functional import crop, hflip
from torchvision.utils import save_image
from model import ELLA
from transformers import T5EncoderModel, T5Tokenizer
import safetensors
if is_wandb_available():
import wandb
# Will error if the minimal version of diffusers is not installed. Remove at your own risks.
# check_min_version("0.26.0.dev0")
# logger = get_logger(__name__)
def import_model_class_from_model_name_or_path(
pretrained_model_name_or_path: str, revision: str, subfolder: str = "text_encoder"
):
text_encoder_config = PretrainedConfig.from_pretrained(
pretrained_model_name_or_path, subfolder=subfolder, revision=revision
)
model_class = text_encoder_config.architectures[0]
if model_class == "CLIPTextModel":
from transformers import CLIPTextModel
return CLIPTextModel
elif model_class == "CLIPTextModelWithProjection":
from transformers import CLIPTextModelWithProjection
return CLIPTextModelWithProjection
else:
raise ValueError(f"{model_class} is not supported.")
def compute_T5_text_embeddings(prompt, text_encoder, tokenizer, MAXLENGTH = 128):
with torch.no_grad():
text_inputs = tokenizer(
prompt,
return_tensors="pt",
add_special_tokens=True,
max_length=MAXLENGTH,
padding="max_length",
truncation=True,
)
text_input_ids = text_inputs.input_ids
attention_mask = text_inputs.attention_mask
text_input_ids = text_input_ids.to(text_encoder.device)
attention_mask = attention_mask.to(text_encoder.device)
encoder_hidden_states = text_encoder(text_input_ids, attention_mask=attention_mask).last_hidden_state
return encoder_hidden_states
def tokenize_prompt(tokenizer, prompt):
text_inputs = tokenizer(
prompt,
padding="max_length",
max_length=tokenizer.model_max_length,
truncation=True,
return_tensors="pt",
)
text_input_ids = text_inputs.input_ids
return text_input_ids
def encode_prompt(text_encoders, tokenizers, prompt, text_input_ids_list=None):
prompt_embeds_list = []
for i, text_encoder in enumerate(text_encoders):
if tokenizers is not None:
tokenizer = tokenizers[i]
text_input_ids = tokenize_prompt(tokenizer, prompt)
else:
assert text_input_ids_list is not None
text_input_ids = text_input_ids_list[i]
prompt_embeds = text_encoder(
text_input_ids.to(text_encoder.device),
output_hidden_states=True,
)
# We are only ALWAYS interested in the pooled output of the final text encoder
pooled_prompt_embeds = prompt_embeds[0]
prompt_embeds = prompt_embeds.hidden_states[-2]
bs_embed, seq_len, _ = prompt_embeds.shape
prompt_embeds = prompt_embeds.view(bs_embed, seq_len, -1)
prompt_embeds_list.append(prompt_embeds)
prompt_embeds = torch.concat(prompt_embeds_list, dim=-1)
pooled_prompt_embeds = pooled_prompt_embeds.view(bs_embed, -1)
return prompt_embeds, pooled_prompt_embeds
def compute_text_embeddings(prompt, text_encoders, tokenizers):
with torch.no_grad():
prompt_embeds, pooled_prompt_embeds = encode_prompt(text_encoders, tokenizers, prompt)
prompt_embeds = prompt_embeds.to(text_encoders[0].device)
pooled_prompt_embeds = pooled_prompt_embeds.to(text_encoders[0].device)
return prompt_embeds, pooled_prompt_embeds
class ImageSizeSampler(Sampler):
def __init__(self, json_mapping,ratio_dict, batch_size, seed = 0):
self.json_mapping = json_mapping
self.batch_size = batch_size
self.size_name = 'h_w'
# Create a list of indices for each image size
self.indices_by_size = {}
random.seed(seed)
for idx, values in enumerate(json_mapping):
h,w = values[self.size_name]
try:
size_ratio = h/w
except:
continue
target_size = self.find_nearest_ratio(ratio_dict, size_ratio)
if target_size not in self.indices_by_size:
self.indices_by_size[target_size] = []
self.indices_by_size[target_size].append(idx)
# Calculate the number of batches
self.num_batches = sum(len(indices) // batch_size for indices in self.indices_by_size.values())
def find_nearest_ratio(self, ratios_dict, target_ratio):
# Calculate absolute differences between target ratio and ratios in the dictionary
differences = {key: abs(value - target_ratio) for key, value in ratios_dict.items()}
# Find the key with the minimum difference
nearest_ratio_key = min(differences, key=differences.get)
return nearest_ratio_key
def __iter__(self):
# Shuffle indices within each image size
for indices in self.indices_by_size.values():
random.shuffle(indices)
# Create batches
batches = []
for indices in self.indices_by_size.values():
drop_last = len(indices) // self.batch_size * self.batch_size # adding drop last to aviod the additional images of each type of size
for i in range(0, drop_last, self.batch_size):
if len(indices[i:i+self.batch_size]) == self.batch_size:
batches.append(indices[i:i+self.batch_size])
# Shuffle batches
random.shuffle(batches)
# Yield indices in each batch
for batch_indices in batches:
yield from batch_indices
def __len__(self):
return self.num_batches
def collate_fn_MyDataset(examples):
crop_top_lefts = [example["crop_top_lefts"] for example in examples]
original_sizes = [example["original_sizes"] for example in examples]
pixel_values = torch.stack([example["pixel_values"] for example in examples])
prompts = [example["prompt"] for example in examples]
pixel_values = pixel_values.to(memory_format=torch.contiguous_format).float()
return {
"pixel_values": pixel_values,
"prompts": prompts,
"original_sizes": original_sizes,
"crop_top_lefts": crop_top_lefts,
}
class FinetuneDataset(Dataset):
"""
A dataset to prepare the instance and class images with the prompts for fine-tuning the model.
It pre-processes the images and the tokenizes prompts.
"""
def __init__(
self,
items_data,
size=1024,
center_crop=False,
proportion_empty_prompts = 0.05,
):
self.size_bucket = size
self.center_crop = center_crop
self.items_data = items_data
self.keys = None
# self.proportion_empty_prompts = proportion_empty_prompts
# #############################################
self.num_instance_images = len(self.items_data)
self._length = self.num_instance_images
# adding data augmentation
# adapted from SDXL finetuning
# self.train_resize = transforms.Resize(self.size, interpolation=transforms.InterpolationMode.BILINEAR)
# self.train_crop = transforms.CenterCrop(self.size) if center_crop else transforms.RandomCrop(self.size)
self.train_crop = transforms.RandomCrop(size=1024) #if center_crop else transforms.RandomCrop(self.size)
self.train_flip = transforms.RandomHorizontalFlip(p=1.0)
self.image_transforms = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.5], [0.5])])
self.img_dir = {
'mj-general':'',
'laion':'/',
'ptx0_photo': '',
'unsplash': "",
'ye_pop': "",
"dalle3": "",
"journeyDB": "",
"SA1b":"",
}
self.prompt_type_dict = {
"laion": [["new_caption", "prompt"],"ratio"],
"mj-general":[["new_caption", "prompt"],"ratio"],
"ptx0_photo": [["prompt","prompt"],"ratio"],
"unsplash":[["prompt", "prompt"],"ratio"],
"ye_pop": [["llava_caption", "cogvlm_caption"],"length"],
"journeyDB": [["caption","prompt"],"ratio"],
"dalle3": [["short_caption", "long_caption"], "length"],
"SA1b": [["llava_prompt", "llava_prompt"], "ratio"],
}
self.REPEATED_OPENINGS = [
('The image showcases ', ''),
('The image portrays ', ''),
('The image appears to be ', ''),
('The image is ', ''),
('The image depicts ', ''),
('The image features ', ''),
('The image captures ', ''),
('The image shows ', ''),
('The image displays ', ''),
('The image presents ', ''),
('This image showcases ', ''),
('This image portrays ', ''),
('This image appears to be ', ''),
('This image is ', ''),
('This image depicts ', ''),
('This image features ', ''),
('This image captures ', ''),
('This image shows ', ''),
('This image displays ', ''),
('This image presents ', ''),
('In this picture, ', ''),
('In this artwork, ', 'Artwork of '),
('In this illustration, ', 'Illustration of '),
('In this depiction, ', ''),
('In this piece, ', ''),
('In this image, ', ''),
('In this art piece, ', 'Art of '),
('In this scene, ', ''),
('In the picture, ', ''),
('In the artwork, ', 'Artwork of '),
('In the illustration, ', 'Illustration of '),
('In the depiction, ', ''),
('In the piece, ', ''),
('In the image, ', ''),
('In the art piece, ', 'Art of '),
('In the scene, ', ''),
]
def __len__(self):
return self._length
def get_random(self ):
idx = random.randint(0,len(self.items_data))
return self.__getitem__(idx)
def find_nearest_ratio(self, ratios_dict, target_ratio):
bucket_ratio = {k: v[0]/v[1] for k,v in ratios_dict.items()}
# Calculate absolute differences between target ratio and ratios in the dictionary
differences = {key: abs(value - target_ratio) for key, value in bucket_ratio.items()}
# Find the key with the minimum difference
nearest_ratio_key = min(differences, key=differences.get)
return nearest_ratio_key
def postprocess_caption(self, caption):
for often_repeated, replacer in self.REPEATED_OPENINGS:
if often_repeated in caption:
caption = caption.replace(often_repeated, replacer, 1).capitalize()
return caption
def get_prompt(self, item):
[prompt_key0,prompt_key1], select_type = self.prompt_type_dict[item['type']]
if select_type == 'ratio':
prompt_key = prompt_key1 if random.random() >= 0.95 else prompt_key0
elif select_type == 'length':
word0 = len(item[prompt_key0].split(" "))
word1 = len(item[prompt_key1].split(" "))
if word0 <= 100 and word1 <= 100:
if word0 < word1:
prompt_key = prompt_key1
else:
prompt_key = prompt_key0
else:
if word0 <= word1:
prompt_key = prompt_key0
else:
prompt_key = prompt_key1
prompt = item[prompt_key]
refine_prompt = self.postprocess_caption(prompt)
return refine_prompt
def __getitem__(self, index):
try:
# y1, x1 = 0,0
if self.keys is not None:
subitem = self.items_data[self.keys[index % self.num_instance_images]]
else:
subitem = self.items_data[index % self.num_instance_images]
imagepath = os.path.join(self.img_dir[subitem['type']], subitem['source'])
instance_text = self.get_prompt(subitem)#subitem["prompt"]
example = {}
instance_image = Image.open(imagepath)
size_ratio = subitem['h_w'][0] / subitem['h_w'][1]
target_size_type = self.find_nearest_ratio(self.size_bucket, size_ratio)
H,W = self.size_bucket[target_size_type]
example["original_sizes"] = (instance_image.height, instance_image.width)
if not instance_image.mode == "RGB":
instance_image = instance_image.convert("RGB")
if subitem['type'] == "mj-general" or subitem['type'] == "journeyDB" or subitem['type'] == "dalle3":
ratio = random.uniform(1, np.min([1.0 / np.max([H/subitem['h_w'][0], W/subitem['h_w'][1]]), 1.2]))
else:
ratio = random.uniform(1.02, np.min([1.0 / np.max([H/subitem['h_w'][0], W/subitem['h_w'][1]]), 1.2]))
if H > W/subitem['h_w'][1] * subitem['h_w'][0]:
instance_image = instance_image.resize((int(H*ratio),int(H/subitem['h_w'][0] * subitem['h_w'][1]*ratio) + 1))
else:
instance_image = instance_image.resize((int(W/subitem['h_w'][1] * subitem['h_w'][0] *ratio) + 1,int(W*ratio)))
# Random Crop
# y1, x1, h, w = self.train_crop.get_params(instance_image, (W, H))
# instance_image = crop(instance_image, y1, x1, h, w)
# center Crop
y1 = np.max([(instance_image.size[1] - W) //2 -1,0])
x1 = np.max([(instance_image.size[0] - H) //2 -1,0])
instance_image = crop(instance_image, y1, x1,W, H)
if random.random() < 0.5:
# flip
x1 = instance_image.width - x1
instance_image = hflip(instance_image)
if random.random()> 0.9:
instance_text = ''
example["crop_top_lefts"] = (y1, x1)
example["PIL_images"] = instance_image
example["pixel_values"] = self.image_transforms(instance_image)
example["prompt"] = instance_text
return example
except:
import traceback
logger.error(f"########################################### {traceback.format_exc()} {index}#########################################")
# return self.get_random()
def save_model_card(repo_id: str, image_logs=None, base_model=str, repo_folder=None):
img_str = ""
if image_logs is not None:
img_str = "You can find some example images below.\n"
for i, log in enumerate(image_logs):
images = log["images"]
validation_prompt = log["validation_prompt"]
validation_image = log["validation_image"]
validation_image.save(os.path.join(repo_folder, "image_control.png"))
img_str += f"prompt: {validation_prompt}\n"
images = [validation_image] + images
make_image_grid(images, 1, len(images)).save(os.path.join(repo_folder, f"images_{i}.png"))
img_str += f"![images_{i})](./images_{i}.png)\n"
yaml = f"""
---
license: openrail++
base_model: {base_model}
tags:
- stable-diffusion-xl
- stable-diffusion-xl-diffusers
- text-to-image
- diffusers
- controlnet
inference: true
---
"""
model_card = f"""
# controlnet-{repo_id}
These are controlnet weights trained on {base_model} with new type of conditioning.
{img_str}
"""
with open(os.path.join(repo_folder, "README.md"), "w") as f:
f.write(yaml + model_card)
def parse_args(input_args=None):
parser = argparse.ArgumentParser(description="Simple example of a ELLA training script.")
parser.add_argument(
"--pretrained_model_name_or_path",
type=str,
default="stabilityai/stable-diffusion-xl-base-1.0",
required=True,
help="Path to pretrained model or model identifier from huggingface.co/models.",
)
parser.add_argument(
"--pretrained_vae_model_name_or_path",
type=str,
default="madebyollin/sdxl-vae-fp16-fix",
help="Path to an improved VAE to stabilize training. For more details check out: https://github.com/huggingface/diffusers/pull/4038.",
)
parser.add_argument(
"--pretrained_T5_model_or_path",
type=str,
default="google/flan-t5-xl",
help="Path to an improved VAE to stabilize training. For more details check out: https://github.com/huggingface/diffusers/pull/4038.",
)
parser.add_argument(
"--num_latents",
type=int,
default=77,
help="Path to an improved VAE to stabilize training. For more details check out: https://github.com/huggingface/diffusers/pull/4038.",
)
parser.add_argument(
"--pretrained_ELLA_name_or_path",
type=str,
default=None,
help="Path to pretrained controlnet model or model identifier from huggingface.co/models."
" If not specified controlnet weights are initialized from unet.",
)
parser.add_argument(
"--use_clip_concat",
action="store_true",
help="To show if we concate the text embeding, later",
)
parser.add_argument(
"--variant",
type=str,
default='fp16',
help="Variant of the model files of the pretrained model identifier from huggingface.co/models, 'e.g.' fp16",
)
parser.add_argument(
"--revision",
type=str,
default=None,
required=False,
help="Revision of pretrained model identifier from huggingface.co/models.",
)
parser.add_argument(
"--tokenizer_name",
type=str,
default=None,
help="Pretrained tokenizer name or path if not the same as model_name",
)
parser.add_argument(
"--img_dir",
type=str,
default=None,
help="The input image directory.",
)
parser.add_argument(
"--cond_img_path",
type=str,
default=None,
help="The input condition image directory.",
)
parser.add_argument(
"--dataset_file",
type=str,
default=None,
help="The input dataset file.",
)
parser.add_argument(
"--output_dir",
type=str,
default="controlnet-model",
help="The output directory where the model predictions and checkpoints will be written.",
)
parser.add_argument(
"--cache_dir",
type=str,
default='',
help="The directory where the downloaded models and datasets will be stored.",
)
parser.add_argument("--seed", type=int, default=None, help="A seed for reproducible training.")
parser.add_argument(
"--resolution",
type=int,
default=512,
help=(
"The resolution for input images, all the images in the train/validation dataset will be resized to this"
" resolution"
),
)
parser.add_argument(
"--crops_coords_top_left_h",
type=int,
default=0,
help=("Coordinate for (the height) to be included in the crop coordinate embeddings needed by SDXL UNet."),
)
parser.add_argument(
"--crops_coords_top_left_w",
type=int,
default=0,
help=("Coordinate for (the height) to be included in the crop coordinate embeddings needed by SDXL UNet."),
)
parser.add_argument(
"--train_batch_size", type=int, default=4, help="Batch size (per device) for the training dataloader."
)
parser.add_argument("--num_train_epochs", type=int, default=10)
parser.add_argument(
"--max_train_steps",
type=int,
default=None,
help="Total number of training steps to perform. If provided, overrides num_train_epochs.",
)
parser.add_argument(
"--checkpointing_steps",
type=int,
default=500,
help=(
"Save a checkpoint of the training state every X updates. Checkpoints can be used for resuming training via `--resume_from_checkpoint`. "
"In the case that the checkpoint is better than the final trained model, the checkpoint can also be used for inference."
"Using a checkpoint for inference requires separate loading of the original pipeline and the individual checkpointed model components."
"See https://huggingface.co/docs/diffusers/main/en/training/dreambooth#performing-inference-using-a-saved-checkpoint for step by step"
"instructions."
),
)
parser.add_argument(
"--checkpoints_total_limit",
type=int,
default=2,
help=("Max number of checkpoints to store."),
)
parser.add_argument(
"--resume_from_checkpoint",
type=str,
default=None,
help=(
"Whether training should be resumed from a previous checkpoint. Use a path saved by"
' `--checkpointing_steps`, or `"latest"` to automatically select the last available checkpoint.'
),
)
parser.add_argument(
"--gradient_accumulation_steps",
type=int,
default=1,
help="Number of updates steps to accumulate before performing a backward/update pass.",
)
parser.add_argument(
"--gradient_checkpointing",
action="store_true",
help="Whether or not to use gradient checkpointing to save memory at the expense of slower backward pass.",
)
parser.add_argument(
"--learning_rate",
type=float,
default=5e-6,
help="Initial learning rate (after the potential warmup period) to use.",
)
parser.add_argument(
"--scale_lr",
action="store_true",
default=False,
help="Scale the learning rate by the number of GPUs, gradient accumulation steps, and batch size.",
)
parser.add_argument(
"--lr_scheduler",
type=str,
default="constant",
help=(
'The scheduler type to use. Choose between ["linear", "cosine", "cosine_with_restarts", "polynomial",'
' "constant", "constant_with_warmup"]'
),
)
parser.add_argument(
"--lr_warmup_steps", type=int, default=500, help="Number of steps for the warmup in the lr scheduler."
)
parser.add_argument(
"--lr_num_cycles",
type=int,
default=1,
help="Number of hard resets of the lr in cosine_with_restarts scheduler.",
)
parser.add_argument("--lr_power", type=float, default=1.0, help="Power factor of the polynomial scheduler.")
parser.add_argument(
"--use_8bit_adam", action="store_true", help="Whether or not to use 8-bit Adam from bitsandbytes."
)
parser.add_argument(
"--dataloader_num_workers",
type=int,
default=0,
help=(
"Number of subprocesses to use for data loading. 0 means that the data will be loaded in the main process."
),
)
parser.add_argument("--adam_beta1", type=float, default=0.9, help="The beta1 parameter for the Adam optimizer.")
parser.add_argument("--adam_beta2", type=float, default=0.999, help="The beta2 parameter for the Adam optimizer.")
parser.add_argument("--adam_weight_decay", type=float, default=1e-2, help="Weight decay to use.")
parser.add_argument("--adam_epsilon", type=float, default=1e-08, help="Epsilon value for the Adam optimizer")
parser.add_argument("--max_grad_norm", default=1.0, type=float, help="Max gradient norm.")
parser.add_argument("--push_to_hub", action="store_true", help="Whether or not to push the model to the Hub.")
parser.add_argument("--hub_token", type=str, default=None, help="The token to use to push to the Model Hub.")
parser.add_argument(
"--hub_model_id",
type=str,
default=None,
help="The name of the repository to keep in sync with the local `output_dir`.",
)
parser.add_argument(
"--logging_dir",
type=str,
default="logs",
help=(
"[TensorBoard](https://www.tensorflow.org/tensorboard) log directory. Will default to"
" *output_dir/runs/**CURRENT_DATETIME_HOSTNAME***."
),
)
parser.add_argument(
"--allow_tf32",
action="store_true",
help=(
"Whether or not to allow TF32 on Ampere GPUs. Can be used to speed up training. For more information, see"
" https://pytorch.org/docs/stable/notes/cuda.html#tensorfloat-32-tf32-on-ampere-devices"
),
)
parser.add_argument(
"--report_to",
type=str,
default="wandb",
help=(
'The integration to report the results and logs to. Supported platforms are `"tensorboard"`'
' (default), `"wandb"` and `"comet_ml"`. Use `"all"` to report to all integrations.'
),
)
parser.add_argument(
"--mixed_precision",
type=str,
default=None,
choices=["no", "fp16", "bf16"],
help=(
"Whether to use mixed precision. Choose between fp16 and bf16 (bfloat16). Bf16 requires PyTorch >="
" 1.10.and an Nvidia Ampere GPU. Default to the value of accelerate config of the current system or the"
" flag passed with the `accelerate.launch` command. Use this argument to override the accelerate config."
),
)
parser.add_argument(
"--enable_xformers_memory_efficient_attention", action="store_true", help="Whether or not to use xformers."
)
parser.add_argument(
"--set_grads_to_none",
action="store_true",
help=(
"Save more memory by using setting grads to None instead of zero. Be aware, that this changes certain"
" behaviors, so disable this argument if it causes any problems. More info:"
" https://pytorch.org/docs/stable/generated/torch.optim.Optimizer.zero_grad.html"
),
)
parser.add_argument(
"--dataset_name",
type=str,
default=None,
help=(
"The name of the Dataset (from the HuggingFace hub) to train on (could be your own, possibly private,"
" dataset). It can also be a path pointing to a local copy of a dataset in your filesystem,"
" or to a folder containing files that ๐ค Datasets can understand."
),
)
parser.add_argument(
"--dataset_config_name",
type=str,
default=None,
help="The config of the Dataset, leave as None if there's only one config.",
)
parser.add_argument(
"--image_column", type=str, default="image", help="The column of the dataset containing the target image."
)
parser.add_argument(
"--conditioning_image_column",
type=str,
default="conditioning_image",
help="The column of the dataset containing the controlnet conditioning image.",
)
parser.add_argument(
"--caption_column",
type=str,
default="text",
help="The column of the dataset containing a caption or a list of captions.",
)
parser.add_argument(
"--max_train_samples",
type=int,
default=None,
help=(
"For debugging purposes or quicker training, truncate the number of training examples to this "
"value if set."
),
)
parser.add_argument(
"--proportion_empty_prompts",
type=float,
default=0,
help="Proportion of image prompts to be replaced with empty strings. Defaults to 0 (no prompt replacement).",
)
parser.add_argument(
"--validation_prompt",
type=str,
default=None,
nargs="+",
help=(
"A set of prompts evaluated every `--validation_steps` and logged to `--report_to`."
" Provide either a matching number of `--validation_image`s, a single `--validation_image`"
" to be used with all prompts, or a single prompt that will be used with all `--validation_image`s."
),
)
parser.add_argument(
"--validation_image",
type=str,
default=None,
nargs="+",
help=(
"A set of paths to the controlnet conditioning image be evaluated every `--validation_steps`"
" and logged to `--report_to`. Provide either a matching number of `--validation_prompt`s, a"
" a single `--validation_prompt` to be used with all `--validation_image`s, or a single"
" `--validation_image` that will be used with all `--validation_prompt`s."
),
)
parser.add_argument(
"--num_validation_images",
type=int,
default=1,
help="Number of images to be generated for each `--validation_image`, `--validation_prompt` pair",
)
parser.add_argument(
"--validation_steps",
type=int,
default=100,
help=(
"Run validation every X steps. Validation consists of running the prompt"
" `args.validation_prompt` multiple times: `args.num_validation_images`"
" and logging the images."
),
)
parser.add_argument(
"--tracker_project_name",
type=str,
default="sd_xl_train_controlnet",
help=(
"The `project_name` argument passed to Accelerator.init_trackers for"
" more information see https://huggingface.co/docs/accelerate/v0.17.0/en/package_reference/accelerator#accelerate.Accelerator"
),
)
parser.add_argument(
"--tracker_run_name",
type=str,
default="sd_xl_train_run",
help=(
"The `run_name` argument passed to Accelerator.init_trackers for"
),
)
if input_args is not None:
args = parser.parse_args(input_args)
else:
args = parser.parse_args()
if args.proportion_empty_prompts < 0 or args.proportion_empty_prompts > 1:
raise ValueError("`--proportion_empty_prompts` must be in the range [0, 1].")
if args.validation_prompt is not None and args.validation_image is None:
raise ValueError("`--validation_image` must be set if `--validation_prompt` is set")
if args.validation_prompt is None and args.validation_image is not None:
raise ValueError("`--validation_prompt` must be set if `--validation_image` is set")
if (
args.validation_image is not None
and args.validation_prompt is not None
and len(args.validation_image) != 1
and len(args.validation_prompt) != 1
and len(args.validation_image) != len(args.validation_prompt)
):
raise ValueError(
"Must provide either 1 `--validation_image`, 1 `--validation_prompt`,"
" or the same number of `--validation_prompt`s and `--validation_image`s"
)
if args.resolution % 8 != 0:
raise ValueError(
"`--resolution` must be divisible by 8 for consistently sized encoded images between the VAE and the controlnet encoder."
)
return args
def collate_fn(examples):
try:
crop_top_lefts = [example["crop_top_lefts"] for example in examples]
original_sizes = [example["original_sizes"] for example in examples]
prompts = [example["prompt"]for example in examples]
target_size = examples[0]["PIL_images"].size
pixel_values = torch.stack([example["pixel_values"] for example in examples])
pixel_values = pixel_values.to(memory_format=torch.contiguous_format).float()
return {
"pixel_values": pixel_values,
"prompts" : prompts,
"original_sizes": original_sizes,
"crop_top_lefts": crop_top_lefts,
"target_size":target_size
}
except:
return None
def main(args):
logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s")
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(logFormatter)
logger.addHandler(consoleHandler)
args.output_dir = '{}_aspect_ratio_num_latents_{}_LR_{}_BS_{}_resolution_{}_accumulation_{}'.format(args.output_dir,args.num_latents, args.learning_rate, args.train_batch_size, args.resolution, args.gradient_accumulation_steps)
if args.use_clip_concat:
args.output_dir += "_clip_concat"
logging_dir = os.path.join(args.output_dir, "logging")
os.makedirs(args.output_dir, exist_ok= True)
os.makedirs(logging_dir, exist_ok= True)
with open(os.path.join(args.output_dir,'commandline_args.txt'), 'w') as f:
f.write(str(vars(args)))
src = os.path.realpath(__file__)
dst = os.path.join(args.output_dir, 'running_script.toml')
shutil.copyfile(src, dst)
accelerator_project_config = ProjectConfiguration(project_dir=args.output_dir, logging_dir=logging_dir)
accelerator = Accelerator(
gradient_accumulation_steps=args.gradient_accumulation_steps,
mixed_precision=args.mixed_precision,
log_with=args.report_to,
project_config=accelerator_project_config,
)
# Make one log on every process with the configuration for debugging.
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
datefmt="%m/%d/%Y %H:%M:%S",
level=logging.INFO,
)
logger.info(accelerator.state)
if accelerator.is_local_main_process:
transformers.utils.logging.set_verbosity_warning()
diffusers.utils.logging.set_verbosity_info()
else:
transformers.utils.logging.set_verbosity_error()
diffusers.utils.logging.set_verbosity_error()
# If passed along, set the training seed now.
# if args.seed is not None:
# set_seed(args.seed)
# Handle the repository creation
if accelerator.is_main_process:
if args.output_dir is not None:
os.makedirs(args.output_dir, exist_ok=True)
if args.push_to_hub:
repo_id = create_repo(
repo_id=args.hub_model_id or Path(args.output_dir).name, exist_ok=True, token=args.hub_token
).repo_id
# Load scheduler and models
noise_scheduler = DDPMScheduler.from_pretrained(args.pretrained_model_name_or_path, subfolder="scheduler")
vae_path = (
args.pretrained_model_name_or_path
if args.pretrained_vae_model_name_or_path is None
else args.pretrained_vae_model_name_or_path
)
assert vae_path != args.pretrained_model_name_or_path, logger.error( "wrong VAE on fp16 format")
vae = AutoencoderKL.from_pretrained(
vae_path,
revision=args.revision,
force_upcast=False,
)
unet = UNet2DConditionModel.from_pretrained(
args.pretrained_model_name_or_path, subfolder="unet", revision=args.revision
)
T5_encoder = T5EncoderModel.from_pretrained(args.pretrained_T5_model_or_path)
T5_tokenizer = T5Tokenizer.from_pretrained(args.pretrained_T5_model_or_path)
if args.use_clip_concat:
# Load clip
tokenizer_one = AutoTokenizer.from_pretrained(
args.pretrained_model_name_or_path, subfolder="tokenizer", revision=args.revision, use_fast=False
)
tokenizer_two = AutoTokenizer.from_pretrained(
args.pretrained_model_name_or_path, subfolder="tokenizer_2", revision=args.revision, use_fast=False
)
# import correct text encoder classes
text_encoder_cls_one = import_model_class_from_model_name_or_path(
args.pretrained_model_name_or_path, args.revision
)
text_encoder_cls_two = import_model_class_from_model_name_or_path(
args.pretrained_model_name_or_path, args.revision, subfolder="text_encoder_2"
)
text_encoder_one = text_encoder_cls_one.from_pretrained(
args.pretrained_model_name_or_path, subfolder="text_encoder", revision=args.revision
)
text_encoder_two = text_encoder_cls_two.from_pretrained(
args.pretrained_model_name_or_path, subfolder="text_encoder_2", revision=args.revision
)
text_encoder_one.requires_grad_(False)
text_encoder_two.requires_grad_(False)
if args.pretrained_ELLA_name_or_path:
ella = ELLA(width=2048, num_latents = args.num_latents)
if args.pretrained_ELLA_name_or_path.endswith(".safetensors"):
safetensors.torch.load_model(ella, args.pretrained_ELLA_name_or_path, strict=True)
logger.info("Loading existing ELLA weights")
else:
state_dict = torch.load(args.pretrained_ELLA_name_or_path)
msg = ella.load_state_dict(state_dict, strict=True)
logger.info(f"The state of loading models: {msg}")
else:
ella = ELLA(width=2048, num_latents = args.num_latents)
logger.info("Initializing ELLA weights from unet")
# # `accelerate` 0.16.0 will have better support for customized saving
# if version.parse(accelerate.__version__) >= version.parse("0.16.0"):
# # create custom saving & loading hooks so that `accelerator.save_state(...)` serializes in a nice format
# def save_model_hook(models, weights, output_dir):
# if accelerator.is_main_process:
# i = len(weights) - 1
# while len(weights) > 0:
# weights.pop()
# model = models[i]
# sub_dir = "controlnet"
# model.save_pretrained(os.path.join(output_dir, sub_dir))
# i -= 1
# def load_model_hook(models, input_dir):
# while len(models) > 0:
# # pop models so that they are not loaded again
# model = models.pop()
# # load diffusers style into model
# model.register_to_config(**load_model.config)
# model.load_state_dict(load_model.state_dict())
# del load_model
# accelerator.register_save_state_pre_hook(save_model_hook)
# accelerator.register_load_state_pre_hook(load_model_hook)
vae.requires_grad_(False)
T5_encoder.requires_grad_(False)
unet.requires_grad_(False)
ella.train()
if args.enable_xformers_memory_efficient_attention:
if is_xformers_available():
import xformers
xformers_version = version.parse(xformers.__version__)
if xformers_version == version.parse("0.0.16"):
logger.warn(
"xFormers 0.0.16 cannot be used for training in some GPUs. If you observe problems during training, please update xFormers to at least 0.0.17. See https://huggingface.co/docs/diffusers/main/en/optimization/xformers for more details."
)
unet.enable_xformers_memory_efficient_attention()
else:
raise ValueError("xformers is not available. Make sure it is installed correctly")
if args.gradient_checkpointing:
unet.enable_gradient_checkpointing()
# Check that all trainable models are in full precision
low_precision_error_string = (
" Please make sure to always have all model weights in full float32 precision when starting training - even if"
" doing mixed precision training, copy of the weights should still be float32."
)
# Enable TF32 for faster training on Ampere GPUs,
# cf https://pytorch.org/docs/stable/notes/cuda.html#tensorfloat-32-tf32-on-ampere-devices
if args.allow_tf32:
torch.backends.cuda.matmul.allow_tf32 = True
if args.scale_lr:
args.learning_rate = (
args.learning_rate * args.gradient_accumulation_steps * args.train_batch_size * accelerator.num_processes
)
if "optimizer" not in accelerator.state.deepspeed_plugin.deepspeed_config:
accelerator.state.deepspeed_plugin.deepspeed_config["optimizer"] = {
"type": "AdamW",
"params": {
"lr": args.learning_rate,
"betas": [args.adam_beta1, args.adam_beta2],
"eps": args.adam_epsilon,
"weight_decay": args.adam_weight_decay,
},
}
if "scheduler" not in accelerator.state.deepspeed_plugin.deepspeed_config:
accelerator.state.deepspeed_plugin.deepspeed_config["scheduler"] = {
"type": "WarmupLR",
"params": {
"warmup_min_lr": 0,
"warmup_max_lr": args.learning_rate,
"warmup_num_steps": args.lr_warmup_steps,
},
}
logger.info("Using DeepSpeed optimizer.")
# Optimizer creation
params_to_optimize = ella.parameters()
optimizer_class = accelerate.utils.DummyOptim
optimizer = optimizer_class(params_to_optimize)
lr_scheduler = accelerate.utils.DummyScheduler(
optimizer,
total_num_steps=args.max_train_steps,
warmup_num_steps=args.lr_warmup_steps,
)
# For mixed precision training we cast the text_encoder and vae weights to half-precision
# as these models are only used for inference, keeping weights in full precision is not required.
weight_dtype = torch.float32
if accelerator.mixed_precision == "fp16":
weight_dtype = torch.float16
elif accelerator.mixed_precision == "bf16":
weight_dtype = torch.bfloat16
# Move vae, unet and text_encoder to device and cast to weight_dtype
# The VAE is in float32 to avoid NaN losses.
if args.pretrained_vae_model_name_or_path is not None:
vae.to(accelerator.device, dtype=weight_dtype)
else:
vae.to(accelerator.device, dtype=torch.float32)
unet.to(accelerator.device, dtype=weight_dtype)
# ella.to(accelerator.device, dtype=weight_dtype)
T5_encoder.to(accelerator.device, dtype=weight_dtype)
unet.to(accelerator.device, dtype=weight_dtype)
if args.use_clip_concat:
text_encoder_one.to(accelerator.device, dtype=weight_dtype)
text_encoder_two.to(accelerator.device, dtype=weight_dtype)
tokenizers = [tokenizer_one, tokenizer_two]
text_encoders = [text_encoder_one, text_encoder_two]
logger.info('loading dataset json file')
items = json.load(open(args.dataset_file,'r'))
"""
# train_dataset = MyDataset( accelerator, args, items_data=items)
train_dataset = MyDataset( accelerator, args, items_data=items)
gc.collect()
torch.cuda.empty_cache()
train_dataloader = torch.utils.data.DataLoader(
train_dataset,
shuffle=True,
collate_fn=collate_fn_MyDataset,
batch_size=args.train_batch_size,
num_workers=args.dataloader_num_workers,
pin_memory=True,
)
"""
bucket_size = {
"4:1": [2048,512],
"10:3":[1728,576],
"12:5":[1536,640],
"21:9":[1472, 704],
"16:9": [1344,768],
"4:3": [1088,896],
"1:1": [1024,1024],
"3:4": [896,1088],
"9:16":[768,1344],
"9:21":[704, 1472],
"5:12": [640,1536],
"5:15":[576,1728],
"5:21": [512,2048],
}
bucket_ratio = {k: v[0]/v[1] for k,v in bucket_size.items()}
train_dataset = FinetuneDataset(
items_data=items ,
size=bucket_size,
center_crop=False,
proportion_empty_prompts = args.proportion_empty_prompts
)
sampler = ImageSizeSampler(items, bucket_ratio, args.train_batch_size, seed=args.seed)
train_dataloader = torch.utils.data.DataLoader(
train_dataset,
# shuffle=True,
collate_fn=collate_fn,
batch_size=args.train_batch_size,
sampler = sampler,
num_workers=args.dataloader_num_workers,
)
gc.collect()
torch.cuda.empty_cache()
# Scheduler and math around the number of training steps.
overrode_max_train_steps = False
num_update_steps_per_epoch = math.ceil(len(train_dataloader) / args.gradient_accumulation_steps)
if args.max_train_steps is None:
args.max_train_steps = args.num_train_epochs * num_update_steps_per_epoch
overrode_max_train_steps = True
# lr_scheduler = get_scheduler(
# args.lr_scheduler,
# optimizer=optimizer,
# num_warmup_steps=args.lr_warmup_steps * accelerator.num_processes,
# num_training_steps=args.max_train_steps * accelerator.num_processes,
# num_cycles=args.lr_num_cycles,
# power=args.lr_power,
# )
# Prepare everything with our `accelerator`.
ella, optimizer, train_dataloader, lr_scheduler = accelerator.prepare(
ella, optimizer, train_dataloader, lr_scheduler
)
# We need to recalculate our total training steps as the size of the training dataloader may have changed.
num_update_steps_per_epoch = math.ceil(len(train_dataloader) / args.gradient_accumulation_steps)
if overrode_max_train_steps:
args.max_train_steps = args.num_train_epochs * num_update_steps_per_epoch
# Afterwards we recalculate our number of training epochs
args.num_train_epochs = math.ceil(args.max_train_steps / num_update_steps_per_epoch)
# We need to initialize the trackers we use, and also store our configuration.
# The trackers initializes automatically on the main process.
if accelerator.is_main_process:
tracker_config = dict(vars(args))
# tensorboard cannot handle list types for config
tracker_config.pop("validation_prompt")
tracker_config.pop("validation_image")
# accelerator.init_trackers(args.tracker_project_name, config=tracker_config)
accelerator.init_trackers(args.tracker_project_name,
config=tracker_config,
init_kwargs={
"wandb": {
"name": args.tracker_run_name,
"id": "%s"%time.strftime('%X %x %Z').replace(':','_').replace(' ','__').replace('/','_'),
"resume": "allow",
"allow_val_change": True,
"dir": args.output_dir
}
},
)
# Train!
total_batch_size = args.train_batch_size * accelerator.num_processes * args.gradient_accumulation_steps
logger.info("***** Running training *****")
logger.info(f" Num examples = {len(train_dataset)}")
logger.info(f" Num batches each epoch = {len(train_dataloader)}")
logger.info(f" Num Epochs = {args.num_train_epochs}")
logger.info(f" Instantaneous batch size per device = {args.train_batch_size}")
logger.info(f" Total train batch size (w. parallel, distributed & accumulation) = {total_batch_size}")
logger.info(f" Gradient Accumulation steps = {args.gradient_accumulation_steps}")
logger.info(f" Total optimization steps = {args.max_train_steps}")
global_step = 0
first_epoch = 0
# Potentially load in the weights and states from a previous save
if args.resume_from_checkpoint:
if args.resume_from_checkpoint != "latest":
path = os.path.basename(args.resume_from_checkpoint)
else:
# Get the most recent checkpoint
dirs = os.listdir(args.output_dir)
dirs = [d for d in dirs if d.startswith("checkpoint")]
dirs = sorted(dirs, key=lambda x: int(x.split("-")[1]))
path = dirs[-1] if len(dirs) > 0 else None
if path is None:
accelerator.print(
f"Checkpoint '{args.resume_from_checkpoint}' does not exist. Starting a new training run."
)
args.resume_from_checkpoint = None
initial_global_step = 0
else:
accelerator.print(f"Resuming from checkpoint {path}")
accelerator.load_state(os.path.join(args.output_dir, path))
global_step = int(path.split("-")[1])
initial_global_step = global_step
first_epoch = global_step // num_update_steps_per_epoch
else:
initial_global_step = 0
progress_bar = tqdm(
range(0, args.max_train_steps),
initial=initial_global_step,
desc="Steps",
# Only show the progress bar once on each machine.
disable=not accelerator.is_local_main_process,
)
image_logs = None
lr = 0.0
for epoch in range(first_epoch, args.num_train_epochs):
for step, batch in enumerate(train_dataloader):
if batch is None:
continue
with accelerator.accumulate(ella):
if args.pretrained_vae_model_name_or_path is not None:
pixel_values = batch["pixel_values"].to(dtype=weight_dtype)
else:
pixel_values = batch["pixel_values"]
latents = vae.encode(pixel_values).latent_dist.sample()
latents = latents * vae.config.scaling_factor
if args.pretrained_vae_model_name_or_path is None:
latents = latents.to(weight_dtype)
# Sample noise that we'll add to the latents
noise = torch.randn_like(latents)
bsz = latents.shape[0]
# Sample a random timestep for each image
timesteps = torch.randint(0, noise_scheduler.config.num_train_timesteps, (bsz,), device=latents.device)
timesteps = timesteps.long()
# Add noise to the latents according to the noise magnitude at each timestep
# (this is the forward diffusion process)
noisy_latents = noise_scheduler.add_noise(latents, noise, timesteps)
# SDXL additional part
def compute_time_ids(original_size, crops_coords_top_left):
# Adapted from pipeline.StableDiffusionXLPipeline._get_add_time_ids
# target_size = (args.resolution, args.resolution)
target_size = batch['target_size']
add_time_ids = list(original_size + crops_coords_top_left + target_size)
add_time_ids = torch.tensor([add_time_ids])
add_time_ids = add_time_ids.to(accelerator.device, dtype=weight_dtype)
return add_time_ids
# def compute_time_ids(original_size, crops_coords_top_left):
# # Adapted from pipeline.StableDiffusionXLPipeline._get_add_time_ids
# target_size = [args.resolution, args.resolution]
# add_time_ids = list(original_size + crops_coords_top_left + target_size)
# add_time_ids = torch.tensor([add_time_ids])
# add_time_ids = add_time_ids.to(accelerator.device, dtype=weight_dtype)
# return add_time_ids
add_time_ids = torch.cat(
[compute_time_ids(s, c) for s, c in zip(batch["original_sizes"], batch["crop_top_lefts"])]
)
#### ella code ########
encoder_hidden_states = compute_T5_text_embeddings(batch["prompts"], T5_encoder, T5_tokenizer)
prompt_embeds,pooled_prompt_embeds = ella(
encoder_hidden_states, timesteps
)
pooled_prompt_embeds = pooled_prompt_embeds.to(accelerator.device)
prompt_embeds = prompt_embeds.to(accelerator.device)
if args.use_clip_concat:
origin_prompt_embeds, _ = compute_text_embeddings(
batch['prompts'], text_encoders, tokenizers
)
origin_prompt_embeds = origin_prompt_embeds.to(accelerator.device)
prompt_embeds = torch.concat([prompt_embeds, origin_prompt_embeds], dim = 1)
#### ella code ########
unet_added_conditions = {"time_ids": add_time_ids}
unet_added_conditions.update({"text_embeds": pooled_prompt_embeds})
# Predict the noise residual
model_pred = unet(
noisy_latents,
timesteps,
encoder_hidden_states=prompt_embeds,
added_cond_kwargs=unet_added_conditions,
).sample
# Get the target for loss depending on the prediction type
if noise_scheduler.config.prediction_type == "epsilon":
target = noise
elif noise_scheduler.config.prediction_type == "v_prediction":
target = noise_scheduler.get_velocity(latents, noise, timesteps)
else:
raise ValueError(f"Unknown prediction type {noise_scheduler.config.prediction_type}")
loss = F.mse_loss(model_pred.float(), target.float(), reduction="mean")
accelerator.backward(loss)
if accelerator.sync_gradients:
params_to_clip = ella.parameters()
accelerator.clip_grad_norm_(params_to_clip, args.max_grad_norm)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad(set_to_none=args.set_grads_to_none)
# Checks if the accelerator has performed an optimization step behind the scenes
if accelerator.sync_gradients:
progress_bar.update(1)
global_step += 1
try:
if accelerator.is_main_process:
if global_step % args.checkpointing_steps == 0:
# _before_ saving state, check if this save would set us over the `checkpoints_total_limit`
if args.checkpoints_total_limit is not None:
checkpoints = os.listdir(args.output_dir)
checkpoints = [d for d in checkpoints if d.startswith("checkpoint")]
checkpoints = sorted(checkpoints, key=lambda x: int(x.split("-")[1]))
# before we save the new checkpoint, we need to have at _most_ `checkpoints_total_limit - 1` checkpoints
if len(checkpoints) >= args.checkpoints_total_limit:
num_to_remove = len(checkpoints) - args.checkpoints_total_limit + 1
removing_checkpoints = checkpoints[0:num_to_remove]
logger.info(
f"{len(checkpoints)} checkpoints already exist, removing {len(removing_checkpoints)} checkpoints"
)
logger.info(f"removing checkpoints: {', '.join(removing_checkpoints)}")
# for removing_checkpoint in removing_checkpoints:
# removing_checkpoint = os.path.join(args.output_dir, removing_checkpoint)
# shutil.rmtree(removing_checkpoint)
if global_step % args.checkpointing_steps == 0:
save_path = os.path.join(args.output_dir, f"checkpoint-{global_step}")
accelerator.save_state(save_path)
logger.info(f"Saved state to {save_path}")
except:
import traceback
logger.error(traceback.format_exc())
if accelerator.is_main_process:
if global_step % args.checkpointing_steps == 0:
state_dict = accelerator.unwrap_model(ella).state_dict()
torch.save(state_dict, os.path.join(args.output_dir, f"pipeline-{global_step}.pth"))
logger.info(f"Saving the checkpoint--- Step={global_step}")
del state_dict
gc.collect()
torch.cuda.empty_cache()
try:
lr = lr_scheduler.get_last_lr()[0]
except Exception as e:
logger.error(
f"Failed to get the last learning rate from the scheduler. Error: {e}"
)
logs = {
"step_loss": loss.detach().item(),
"lr": lr,
# "train_time":it_train_time,
# "data_time":it_data_time,
}
progress_bar.set_postfix(**logs)
accelerator.log(logs, step=global_step)
if global_step >= args.max_train_steps:
break
# Create the pipeline using using the trained modules and save it.
accelerator.wait_for_everyone()
if accelerator.is_main_process:
state_dict = accelerator.unwrap_model(ella).state_dict()
torch.save(state_dict, os.path.join(args.output_dir, f"pipeline", f"checkpoint-{step}.pth"))
logger.info("Finished saving checkpoint")
# controlnet.save_pretrained(os.path.join(args.output_dir, f"pipeline") , safe_serialization=False)
if args.push_to_hub:
save_model_card(
repo_id,
image_logs=image_logs,
base_model=args.pretrained_model_name_or_path,
repo_folder=args.output_dir,
)
upload_folder(
repo_id=repo_id,
folder_path=args.output_dir,
commit_message="End of training",
ignore_patterns=["step_*", "epoch_*"],
)
accelerator.end_training()
if __name__ == "__main__":
args = parse_args()
main(args)
my mistake, George. your question was about an input type mismatch, which everybody knows is due to gradient checkpointing bug that was resolved in diffusers main branch. please accept my apologies.
we also didn't have good luck with SDXL ELLA. probably because of the two text encoders.