TencentQQGYLab/ELLA

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!

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

@plastic0313

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.