diff --git a/app.py b/app.py new file mode 100644 index 0000000000000000000000000000000000000000..5b39cfd2a29bedded4e8aac69833506e3654f0eb --- /dev/null +++ b/app.py @@ -0,0 +1,276 @@ +import torch +import torchvision + +import os +import os.path as osp +import random +from argparse import ArgumentParser +from datetime import datetime + +import gradio as gr + +from foleycrafter.utils.util import build_foleycrafter, read_frames_with_moviepy +from foleycrafter.pipelines.auffusion_pipeline import denormalize_spectrogram +from foleycrafter.pipelines.auffusion_pipeline import Generator +from foleycrafter.models.time_detector.model import VideoOnsetNet +from foleycrafter.models.specvqgan.onset_baseline.utils import torch_utils + +from transformers import CLIPVisionModelWithProjection, CLIPImageProcessor +from huggingface_hub import snapshot_download +from diffusers import DDIMScheduler, EulerDiscreteScheduler, PNDMScheduler + +import soundfile as sf +from moviepy.editor import AudioFileClip, VideoFileClip +os.environ['GRADIO_TEMP_DIR'] = './tmp' + +sample_idx = 0 +scheduler_dict = { + "DDIM": DDIMScheduler, + "Euler": EulerDiscreteScheduler, + "PNDM": PNDMScheduler, +} + +css = """ +.toolbutton { + margin-buttom: 0em 0em 0em 0em; + max-width: 2.5em; + min-width: 2.5em !important; + height: 2.5em; +} +""" + +parser = ArgumentParser() +parser.add_argument("--config", type=str, default="example/config/base.yaml") +parser.add_argument("--server-name", type=str, default="0.0.0.0") +parser.add_argument("--port", type=int, default=11451) +parser.add_argument("--share", action="store_true") + +parser.add_argument("--save-path", default="samples") + +args = parser.parse_args() + + +N_PROMPT = ( + "" +) + +class FoleyController: + def __init__(self): + # config dirs + self.basedir = os.getcwd() + self.model_dir = os.path.join(self.basedir, "models") + self.savedir = os.path.join(self.basedir, args.save_path, datetime.now().strftime("Gradio-%Y-%m-%dT%H-%M-%S")) + self.savedir_sample = os.path.join(self.savedir, "sample") + os.makedirs(self.savedir, exist_ok=True) + + self.device = "cuda" if torch.cuda.is_available() else "cpu" + + self.pipeline = None + + self.loaded = False + + self.load_model() + + def load_model(self): + gr.Info("Start Load Models...") + print("Start Load Models...") + + # download ckpt + pretrained_model_name_or_path = 'auffusion/auffusion-full-no-adapter' + if not os.path.isdir(pretrained_model_name_or_path): + pretrained_model_name_or_path = snapshot_download(pretrained_model_name_or_path, local_dir='models/auffusion') + + fc_ckpt = 'ymzhang319/FoleyCrafter' + if not os.path.isdir(fc_ckpt): + fc_ckpt = snapshot_download(fc_ckpt, local_dir='models/') + + # set model config + temporal_ckpt_path = osp.join(self.model_dir, 'temporal_adapter.ckpt') + + # load vocoder + vocoder_config_path= "./models/auffusion" + self.vocoder = Generator.from_pretrained( + vocoder_config_path, + subfolder="vocoder").to(self.device) + + # load time detector + time_detector_ckpt = osp.join(osp.join(self.model_dir, 'timestamp_detector.pth.tar')) + time_detector = VideoOnsetNet(False) + self.time_detector, _ = torch_utils.load_model(time_detector_ckpt, time_detector, strict=True, device=self.device) + + self.pipeline = build_foleycrafter().to(self.device) + ckpt = torch.load(temporal_ckpt_path) + + # load temporal adapter + if 'state_dict' in ckpt.keys(): + ckpt = ckpt['state_dict'] + load_gligen_ckpt = {} + for key, value in ckpt.items(): + if key.startswith('module.'): + load_gligen_ckpt[key[len('module.'):]] = value + else: + load_gligen_ckpt[key] = value + m, u = self.pipeline.controlnet.load_state_dict(load_gligen_ckpt, strict=False) + print(f"### Control Net missing keys: {len(m)}; \n### unexpected keys: {len(u)};") + + self.image_processor = CLIPImageProcessor() + self.image_encoder = CLIPVisionModelWithProjection.from_pretrained('h94/IP-Adapter', subfolder='models/image_encoder').to(self.device) + + self.pipeline.load_ip_adapter(fc_ckpt, subfolder='semantic', weight_name='semantic_adapter.bin', image_encoder_folder=None) + + gr.Info("Load Finish!") + print("Load Finish!") + self.loaded = True + + return "Load" + + def foley( + self, + input_video, + prompt_textbox, + negative_prompt_textbox, + ip_adapter_scale, + temporal_scale, + sampler_dropdown, + sample_step_slider, + cfg_scale_slider, + seed_textbox, + ): + + vision_transform_list = [ + torchvision.transforms.Resize((128, 128)), + torchvision.transforms.CenterCrop((112, 112)), + torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + ] + video_transform = torchvision.transforms.Compose(vision_transform_list) + if not self.loaded: + raise gr.Error("Error with loading model") + generator = torch.Generator() + if seed_textbox != "": + torch.manual_seed(int(seed_textbox)) + generator.manual_seed(int(seed_textbox)) + max_frame_nums = 15 + frames, duration = read_frames_with_moviepy(input_video, max_frame_nums=max_frame_nums) + if duration >= 10: + duration = 10 + time_frames = torch.FloatTensor(frames).permute(0, 3, 1, 2) + time_frames = video_transform(time_frames) + time_frames = {'frames': time_frames.unsqueeze(0).permute(0, 2, 1, 3, 4)} + preds = self.time_detector(time_frames) + preds = torch.sigmoid(preds) + + # duration + time_condition = [-1 if preds[0][int(i / (1024 / 10 * duration) * max_frame_nums)] < 0.5 else 1 for i in range(int(1024 / 10 * duration))] + time_condition = time_condition + [-1] * (1024 - len(time_condition)) + # w -> b c h w + time_condition = torch.FloatTensor(time_condition).unsqueeze(0).unsqueeze(0).unsqueeze(0).repeat(1, 1, 256, 1) + + images = self.image_processor(images=frames, return_tensors="pt").to(self.device) + image_embeddings = self.image_encoder(**images).image_embeds + image_embeddings = torch.mean(image_embeddings, dim=0, keepdim=True).unsqueeze(0).unsqueeze(0) + neg_image_embeddings = torch.zeros_like(image_embeddings) + image_embeddings = torch.cat([neg_image_embeddings, image_embeddings], dim=1) + self.pipeline.set_ip_adapter_scale(ip_adapter_scale) + sample = self.pipeline( + prompt=prompt_textbox, + negative_prompt=negative_prompt_textbox, + ip_adapter_image_embeds=image_embeddings, + image=time_condition, + controlnet_conditioning_scale=float(temporal_scale), + num_inference_steps=sample_step_slider, + height=256, + width=1024, + output_type="pt", + generator=generator, + ) + name = 'output' + audio_img = sample.images[0] + audio = denormalize_spectrogram(audio_img) + audio = self.vocoder.inference(audio, lengths=160000)[0] + audio_save_path = osp.join(self.savedir_sample, 'audio') + os.makedirs(audio_save_path, exist_ok=True) + audio = audio[:int(duration * 16000)] + + save_path = osp.join(audio_save_path, f'{name}.wav') + sf.write(save_path, audio, 16000) + + audio = AudioFileClip(osp.join(audio_save_path, f'{name}.wav')) + video = VideoFileClip(input_video) + audio = audio.subclip(0, duration) + video.audio = audio + video = video.subclip(0, duration) + video.write_videofile(osp.join(self.savedir_sample, f'{name}.mp4')) + save_sample_path = os.path.join(self.savedir_sample, f"{name}.mp4") + + return save_sample_path + +controller = FoleyController() + +def ui(): + with gr.Blocks(css=css) as demo: + gr.HTML( + "
FoleyCrafter: Bring Silent Videos to Life with Lifelike and Synchronized Sounds
" + ) + with gr.Row(): + gr.Markdown( + "
Project Page  " # noqa + "Paper  " + "Code  " + "Demo
" + ) + + with gr.Column(variant="panel"): + with gr.Row(equal_height=False): + with gr.Column(): + with gr.Row(): + init_img = gr.Video(label="Input Video") + with gr.Row(): + prompt_textbox = gr.Textbox(value='', label="Prompt", lines=1) + with gr.Row(): + negative_prompt_textbox = gr.Textbox(value=N_PROMPT, label="Negative prompt", lines=1) + + with gr.Row(): + sampler_dropdown = gr.Dropdown( + label="Sampling method", + choices=list(scheduler_dict.keys()), + value=list(scheduler_dict.keys())[0], + ) + sample_step_slider = gr.Slider( + label="Sampling steps", value=25, minimum=10, maximum=100, step=1 + ) + + cfg_scale_slider = gr.Slider(label="CFG Scale", value=7.5, minimum=0, maximum=20) + ip_adapter_scale = gr.Slider(label="Visual Content Scale", value=1.0, minimum=0, maximum=1) + temporal_scale = gr.Slider(label="Temporal Align Scale", value=0., minimum=0., maximum=1.0) + + with gr.Row(): + seed_textbox = gr.Textbox(label="Seed", value=42) + seed_button = gr.Button(value="\U0001f3b2", elem_classes="toolbutton") + seed_button.click(fn=lambda x: random.randint(1, 1e8), outputs=[seed_textbox], queue=False) + + generate_button = gr.Button(value="Generate", variant="primary") + + result_video = gr.Video(label="Generated Audio", interactive=False) + + generate_button.click( + fn=controller.foley, + inputs=[ + init_img, + prompt_textbox, + negative_prompt_textbox, + ip_adapter_scale, + temporal_scale, + sampler_dropdown, + sample_step_slider, + cfg_scale_slider, + seed_textbox, + ], + outputs=[result_video], + ) + + return demo + +if __name__ == "__main__": + demo = ui() + demo.queue(3) + demo.launch(server_name=args.server_name, server_port=args.port, share=args.share) \ No newline at end of file diff --git a/configs/auffusion/vocoder/config.json b/configs/auffusion/vocoder/config.json new file mode 100644 index 0000000000000000000000000000000000000000..07860a8422ad8ffd7838b0b87c5a2f7126fbff06 --- /dev/null +++ b/configs/auffusion/vocoder/config.json @@ -0,0 +1,37 @@ +{ + "resblock": "1", + "num_gpus": 0, + "batch_size": 16, + "learning_rate": 0.0002, + "adam_b1": 0.8, + "adam_b2": 0.99, + "lr_decay": 0.999, + "seed": 1234, + + "upsample_rates": [5,4,4,2], + "upsample_kernel_sizes": [11,8,8,4], + "upsample_initial_channel": 512, + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + + "segment_size": 5120, + "num_mels": 256, + "num_freq": 2049, + "n_fft": 2048, + "hop_size": 160, + "win_size": 1024, + + "sampling_rate": 16000, + + "fmin": 0, + "fmax": null, + "fmax_for_loss": null, + + "num_workers": 4, + + "dist_config": { + "dist_backend": "nccl", + "dist_url": "tcp://localhost:54321", + "world_size": 1 + } +} diff --git a/configs/train/train_semantic_adapter.yaml b/configs/train/train_semantic_adapter.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e967440443d7c9c8b51a085f4c32d63f0180c871 --- /dev/null +++ b/configs/train/train_semantic_adapter.yaml @@ -0,0 +1,54 @@ +output_dir: "outputs" + +pretrained_model_path: "" + +motion_module_path: "models/mm_sd_v15_v2.ckpt" + +train_data: + csv_path: "./curated.csv" + audio_fps: 48000 + audio_size: 480000 + +validation_data: + prompts: + - "./data/input/lighthouse.png" + - "./data/input/guitar.png" + - "./data/input/lion.png" + - "./data/input/gun.png" + num_inference_steps: 25 + guidance_scale: 7.5 + sample_size: 512 + +trainable_modules: + - 'to_k_ip' + - 'to_v_ip' + +audio_unet_checkpoint_path: "" + +learning_rate: 1.0e-4 +train_batch_size: 1 # max for mixed +gradient_accumulation_steps: 1 + +max_train_epoch: -1 +max_train_steps: 200000 +checkpointing_epochs: 4000 +checkpointing_steps: 500 + +validation_steps: 3000 +validation_steps_tuple: [2, 50, 300, 1000] + +global_seed: 42 +mixed_precision_training: true + +is_debug: False + +resume_ckpt: "" + +# params for adapter +init_from_ip_adapter: false + +always_null_text: false + +reverse_null_text_prob: true + +frame_wise_condition: true diff --git a/configs/train/train_temporal_adapter.yaml b/configs/train/train_temporal_adapter.yaml new file mode 100644 index 0000000000000000000000000000000000000000..92018e38460bf3c57af8f83bb20a026fad15427a --- /dev/null +++ b/configs/train/train_temporal_adapter.yaml @@ -0,0 +1,48 @@ +output_dir: "outputs" + +pretrained_model_path: "" + +motion_module_path: "models/mm_sd_v15_v2.ckpt" + +train_data: + csv_path: "./curated.csv" + audio_fps: 48000 + audio_size: 480000 + +validation_data: + prompts: + - "./data/input/lighthouse.png" + - "./data/input/guitar.png" + - "./data/input/lion.png" + - "./data/input/gun.png" + num_inference_steps: 25 + guidance_scale: 7.5 + sample_size: 512 + +trainable_modules: + - 'time_conv_in.' + - 'conv_in.' + +video_unet_checkpoint_path: "models/vggsound_unet.ckpt" +audio_unet_checkpoint_path: "" + +learning_rate: 5.0e-5 +train_batch_size: 1 # max for mixed +gradient_accumulation_steps: 1 + +max_train_epoch: -1 +max_train_steps: 500000 +checkpointing_epochs: 4000 +checkpointing_steps: 500 + +validation_steps: 3000 +validation_steps_tuple: [2, 300, 1000] + +global_seed: 42 +mixed_precision_training: true + +is_debug: False + +resume_ckpt: "" + +zero_no_label_mel: false \ No newline at end of file diff --git a/environment.yaml b/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..dddf02b89aa390a24d543ed1ff60413003707022 --- /dev/null +++ b/environment.yaml @@ -0,0 +1,24 @@ +name: foleycrafter +channels: + - pytorch + - nvidia +dependencies: + - python=3.10 + - pytorch=2.2.0 + - torchvision=0.17.0 + - pytorch-cuda=11.8 + - pip + - pip: + - diffusers==0.25.1 + - transformers==4.30.2 + - xformers + - imageio==2.33.1 + - decord==0.6.0 + - einops + - omegaconf + - safetensors + - gradio + - tqdm==4.66.1 + - soundfile==0.12.1 + - wandb + - moviepy==1.0.3 \ No newline at end of file diff --git a/foleycrafter/data/dataset.py b/foleycrafter/data/dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..c7b77b07232caee25bc1fc661cbdaf086ba9e7a1 --- /dev/null +++ b/foleycrafter/data/dataset.py @@ -0,0 +1,175 @@ +import torch +import torchvision.transforms as transforms +from torch.utils.data.dataset import Dataset +import torch.distributed as dist +import torchaudio +import torchvision +import torchvision.io + +import os, io, csv, math, random +import os.path as osp +from pathlib import Path +import numpy as np +import pandas as pd +from einops import rearrange +import glob + +from decord import VideoReader, AudioReader +import decord +from copy import deepcopy +import pickle + +from petrel_client.client import Client +import sys +sys.path.append('./') +from foleycrafter.data import video_transforms + +from foleycrafter.utils.util import \ + random_audio_video_clip, get_full_indices, video_tensor_to_np, get_video_frames +from foleycrafter.utils.spec_to_mel import wav_tensor_to_fbank, read_wav_file_io, load_audio, normalize_wav, pad_wav +from foleycrafter.utils.converter import get_mel_spectrogram_from_audio, pad_spec, normalize, normalize_spectrogram + +def zero_rank_print(s): + if (not dist.is_initialized()) or (dist.is_initialized() and dist.get_rank() == 0): print("### " + s, flush=True) + +@torch.no_grad() +def get_mel(audio_data, audio_cfg): + # mel shape: (n_mels, T) + mel = torchaudio.transforms.MelSpectrogram( + sample_rate=audio_cfg["sample_rate"], + n_fft=audio_cfg["window_size"], + win_length=audio_cfg["window_size"], + hop_length=audio_cfg["hop_size"], + center=True, + pad_mode="reflect", + power=2.0, + norm=None, + onesided=True, + n_mels=64, + f_min=audio_cfg["fmin"], + f_max=audio_cfg["fmax"], + ).to(audio_data.device) + mel = mel(audio_data) + # we use log mel spectrogram as input + mel = torchaudio.transforms.AmplitudeToDB(top_db=None)(mel) + return mel # (T, n_mels) + +def dynamic_range_compression(x, normalize_fun=torch.log, C=1, clip_val=1e-5): + """ + PARAMS + ------ + C: compression factor + """ + return normalize_fun(torch.clamp(x, min=clip_val) * C) + +class CPU_Unpickler(pickle.Unpickler): + def find_class(self, module, name): + if module == 'torch.storage' and name == '_load_from_bytes': + return lambda b: torch.load(io.BytesIO(b), map_location='cpu') + else: + return super().find_class(module, name) + +class AudioSetStrong(Dataset): + # read feature and audio + def __init__( + self, + ): + super().__init__() + self.data_path = 'data/AudioSetStrong/train/feature' + self.data_list = list(self._client.list(self.data_path)) + self.length = len(self.data_list) + # get video feature + self.video_path = 'data/AudioSetStrong/train/video' + vision_transform_list = [ + transforms.Resize((128, 128)), + transforms.CenterCrop((112, 112)), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + ] + self.video_transform = transforms.Compose(vision_transform_list) + + def get_batch(self, idx): + embeds = self.data_list[idx] + mel = embeds['mel'] + save_bsz = mel.shape[0] + audio_info = embeds['audio_info'] + text_embeds = embeds['text_embeds'] + + # audio_info['label_list'] = np.array(audio_info['label_list']) + audio_info_array = np.array(audio_info['label_list']) + prompts = [] + for i in range(save_bsz): + prompts.append(', '.join(audio_info_array[i, :audio_info['event_num'][i]].tolist())) + # import ipdb; ipdb.set_trace() + # read videos + videos = None + for video_name in audio_info['audio_name']: + video_bytes = self._client.Get(osp.join(self.video_path, video_name+'.mp4')) + video_bytes = io.BytesIO(video_bytes) + video_reader = VideoReader(video_bytes) + video = video_reader.get_batch(get_full_indices(video_reader)).asnumpy() + video = get_video_frames(video, 150) + video = torch.from_numpy(video).permute(0, 3, 1, 2).contiguous().float() + video = self.video_transform(video) + video = video.unsqueeze(0) + if videos is None: + videos = video + else: + videos = torch.cat([videos, video], dim=0) + # video = torch.from_numpy(video).permute(0, 3, 1, 2).contiguous() + assert videos is not None, 'no video read' + + return mel, audio_info, text_embeds, prompts, videos + + def __len__(self): + return self.length + + def __getitem__(self, idx): + while True: + try: + mel, audio_info, text_embeds, prompts, videos = self.get_batch(idx) + break + except Exception as e: + zero_rank_print(' >>> load error <<<') + idx = random.randint(0, self.length-1) + sample = dict(mel=mel, audio_info=audio_info, text_embeds=text_embeds, prompts=prompts, videos=videos) + return sample + +class VGGSound(Dataset): + # read feature and audio + def __init__( + self, + ): + super().__init__() + self.data_path = 'data/VGGSound/train/video' + self.visual_data_path = 'data/VGGSound/train/feature' + self.embeds_list = glob.glob(f'{self.data_path}/*.pt') + self.visual_list = glob.glob(f'{self.visual_data_path}/*.pt') + self.length = len(self.embeds_list) + + def get_batch(self, idx): + embeds = torch.load(self.embeds_list[idx], map_location='cpu') + visual_embeds = torch.load(self.visual_list[idx], map_location='cpu') + + # audio_embeds = embeds['audio_embeds'] + visual_embeds = visual_embeds['visual_embeds'] + video_name = embeds['video_name'] + text = embeds['text'] + mel = embeds['mel'] + + audio = mel + + return visual_embeds, audio, text + + def __len__(self): + return self.length + + def __getitem__(self, idx): + while True: + try: + visual_embeds, audio, text = self.get_batch(idx) + break + except Exception as e: + zero_rank_print('load error') + idx = random.randint(0, self.length-1) + sample = dict(visual_embeds=visual_embeds, audio=audio, text=text) + return sample \ No newline at end of file diff --git a/foleycrafter/data/video_transforms.py b/foleycrafter/data/video_transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..909f555105e4851b0da5747e0cdba991060b4428 --- /dev/null +++ b/foleycrafter/data/video_transforms.py @@ -0,0 +1,400 @@ +import torch +import random +import numbers +from torchvision.transforms import RandomCrop, RandomResizedCrop + +def _is_tensor_video_clip(clip): + if not torch.is_tensor(clip): + raise TypeError("clip should be Tensor. Got %s" % type(clip)) + + if not clip.ndimension() == 4: + raise ValueError("clip should be 4D. Got %dD" % clip.dim()) + + return True + + +def crop(clip, i, j, h, w): + """ + Args: + clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W) + """ + if len(clip.size()) != 4: + raise ValueError("clip should be a 4D tensor") + return clip[..., i : i + h, j : j + w] + + +def resize(clip, target_size, interpolation_mode): + if len(target_size) != 2: + raise ValueError(f"target size should be tuple (height, width), instead got {target_size}") + return torch.nn.functional.interpolate(clip, size=target_size, mode=interpolation_mode, align_corners=False) + +def resize_scale(clip, target_size, interpolation_mode): + if len(target_size) != 2: + raise ValueError(f"target size should be tuple (height, width), instead got {target_size}") + _, _, H, W = clip.shape + scale_ = target_size[0] / min(H, W) + return torch.nn.functional.interpolate(clip, scale_factor=scale_, mode=interpolation_mode, align_corners=False) + + +def resized_crop(clip, i, j, h, w, size, interpolation_mode="bilinear"): + """ + Do spatial cropping and resizing to the video clip + Args: + clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W) + i (int): i in (i,j) i.e coordinates of the upper left corner. + j (int): j in (i,j) i.e coordinates of the upper left corner. + h (int): Height of the cropped region. + w (int): Width of the cropped region. + size (tuple(int, int)): height and width of resized clip + Returns: + clip (torch.tensor): Resized and cropped clip. Size is (T, C, H, W) + """ + if not _is_tensor_video_clip(clip): + raise ValueError("clip should be a 4D torch.tensor") + clip = crop(clip, i, j, h, w) + clip = resize(clip, size, interpolation_mode) + return clip + + +def center_crop(clip, crop_size): + if not _is_tensor_video_clip(clip): + raise ValueError("clip should be a 4D torch.tensor") + h, w = clip.size(-2), clip.size(-1) + th, tw = crop_size + if h < th or w < tw: + raise ValueError("height and width must be no smaller than crop_size") + + i = int(round((h - th) / 2.0)) + j = int(round((w - tw) / 2.0)) + return crop(clip, i, j, th, tw) + +def random_shift_crop(clip): + ''' + Slide along the long edge, with the short edge as crop size + ''' + if not _is_tensor_video_clip(clip): + raise ValueError("clip should be a 4D torch.tensor") + h, w = clip.size(-2), clip.size(-1) + + if h <= w: + long_edge = w + short_edge = h + else: + long_edge = h + short_edge =w + + th, tw = short_edge, short_edge + + i = torch.randint(0, h - th + 1, size=(1,)).item() + j = torch.randint(0, w - tw + 1, size=(1,)).item() + return crop(clip, i, j, th, tw) + + +def to_tensor(clip): + """ + Convert tensor data type from uint8 to float, divide value by 255.0 and + permute the dimensions of clip tensor + Args: + clip (torch.tensor, dtype=torch.uint8): Size is (T, C, H, W) + Return: + clip (torch.tensor, dtype=torch.float): Size is (T, C, H, W) + """ + _is_tensor_video_clip(clip) + if not clip.dtype == torch.uint8: + raise TypeError("clip tensor should have data type uint8. Got %s" % str(clip.dtype)) + # return clip.float().permute(3, 0, 1, 2) / 255.0 + return clip.float() / 255.0 + + +def normalize(clip, mean, std, inplace=False): + """ + Args: + clip (torch.tensor): Video clip to be normalized. Size is (T, C, H, W) + mean (tuple): pixel RGB mean. Size is (3) + std (tuple): pixel standard deviation. Size is (3) + Returns: + normalized clip (torch.tensor): Size is (T, C, H, W) + """ + if not _is_tensor_video_clip(clip): + raise ValueError("clip should be a 4D torch.tensor") + if not inplace: + clip = clip.clone() + mean = torch.as_tensor(mean, dtype=clip.dtype, device=clip.device) + print(mean) + std = torch.as_tensor(std, dtype=clip.dtype, device=clip.device) + clip.sub_(mean[:, None, None, None]).div_(std[:, None, None, None]) + return clip + + +def hflip(clip): + """ + Args: + clip (torch.tensor): Video clip to be normalized. Size is (T, C, H, W) + Returns: + flipped clip (torch.tensor): Size is (T, C, H, W) + """ + if not _is_tensor_video_clip(clip): + raise ValueError("clip should be a 4D torch.tensor") + return clip.flip(-1) + + +class RandomCropVideo: + def __init__(self, size): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + + def __call__(self, clip): + """ + Args: + clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W) + Returns: + torch.tensor: randomly cropped video clip. + size is (T, C, OH, OW) + """ + i, j, h, w = self.get_params(clip) + return crop(clip, i, j, h, w) + + def get_params(self, clip): + h, w = clip.shape[-2:] + th, tw = self.size + + if h < th or w < tw: + raise ValueError(f"Required crop size {(th, tw)} is larger than input image size {(h, w)}") + + if w == tw and h == th: + return 0, 0, h, w + + i = torch.randint(0, h - th + 1, size=(1,)).item() + j = torch.randint(0, w - tw + 1, size=(1,)).item() + + return i, j, th, tw + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(size={self.size})" + + +class UCFCenterCropVideo: + def __init__( + self, + size, + interpolation_mode="bilinear", + ): + if isinstance(size, tuple): + if len(size) != 2: + raise ValueError(f"size should be tuple (height, width), instead got {size}") + self.size = size + else: + self.size = (size, size) + + self.interpolation_mode = interpolation_mode + + + def __call__(self, clip): + """ + Args: + clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W) + Returns: + torch.tensor: scale resized / center cropped video clip. + size is (T, C, crop_size, crop_size) + """ + clip_resize = resize_scale(clip=clip, target_size=self.size, interpolation_mode=self.interpolation_mode) + clip_center_crop = center_crop(clip_resize, self.size) + return clip_center_crop + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(size={self.size}, interpolation_mode={self.interpolation_mode}" + +class KineticsRandomCropResizeVideo: + ''' + Slide along the long edge, with the short edge as crop size. And resie to the desired size. + ''' + def __init__( + self, + size, + interpolation_mode="bilinear", + ): + if isinstance(size, tuple): + if len(size) != 2: + raise ValueError(f"size should be tuple (height, width), instead got {size}") + self.size = size + else: + self.size = (size, size) + + self.interpolation_mode = interpolation_mode + + def __call__(self, clip): + clip_random_crop = random_shift_crop(clip) + clip_resize = resize(clip_random_crop, self.size, self.interpolation_mode) + return clip_resize + + +class CenterCropVideo: + def __init__( + self, + size, + interpolation_mode="bilinear", + ): + if isinstance(size, tuple): + if len(size) != 2: + raise ValueError(f"size should be tuple (height, width), instead got {size}") + self.size = size + else: + self.size = (size, size) + + self.interpolation_mode = interpolation_mode + + + def __call__(self, clip): + """ + Args: + clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W) + Returns: + torch.tensor: center cropped video clip. + size is (T, C, crop_size, crop_size) + """ + clip_center_crop = center_crop(clip, self.size) + return clip_center_crop + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(size={self.size}, interpolation_mode={self.interpolation_mode}" + + +class NormalizeVideo: + """ + Normalize the video clip by mean subtraction and division by standard deviation + Args: + mean (3-tuple): pixel RGB mean + std (3-tuple): pixel RGB standard deviation + inplace (boolean): whether do in-place normalization + """ + + def __init__(self, mean, std, inplace=False): + self.mean = mean + self.std = std + self.inplace = inplace + + def __call__(self, clip): + """ + Args: + clip (torch.tensor): video clip must be normalized. Size is (C, T, H, W) + """ + return normalize(clip, self.mean, self.std, self.inplace) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(mean={self.mean}, std={self.std}, inplace={self.inplace})" + + +class ToTensorVideo: + """ + Convert tensor data type from uint8 to float, divide value by 255.0 and + permute the dimensions of clip tensor + """ + + def __init__(self): + pass + + def __call__(self, clip): + """ + Args: + clip (torch.tensor, dtype=torch.uint8): Size is (T, C, H, W) + Return: + clip (torch.tensor, dtype=torch.float): Size is (T, C, H, W) + """ + return to_tensor(clip) + + def __repr__(self) -> str: + return self.__class__.__name__ + + +class RandomHorizontalFlipVideo: + """ + Flip the video clip along the horizontal direction with a given probability + Args: + p (float): probability of the clip being flipped. Default value is 0.5 + """ + + def __init__(self, p=0.5): + self.p = p + + def __call__(self, clip): + """ + Args: + clip (torch.tensor): Size is (T, C, H, W) + Return: + clip (torch.tensor): Size is (T, C, H, W) + """ + if random.random() < self.p: + clip = hflip(clip) + return clip + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(p={self.p})" + +# ------------------------------------------------------------ +# --------------------- Sampling --------------------------- +# ------------------------------------------------------------ +class TemporalRandomCrop(object): + """Temporally crop the given frame indices at a random location. + + Args: + size (int): Desired length of frames will be seen in the model. + """ + + def __init__(self, size): + self.size = size + + def __call__(self, total_frames): + rand_end = max(0, total_frames - self.size - 1) + begin_index = random.randint(0, rand_end) + end_index = min(begin_index + self.size, total_frames) + return begin_index, end_index + + +if __name__ == '__main__': + from torchvision import transforms + import torchvision.io as io + import numpy as np + from torchvision.utils import save_image + import os + + vframes, aframes, info = io.read_video( + filename='./v_Archery_g01_c03.avi', + pts_unit='sec', + output_format='TCHW' + ) + + trans = transforms.Compose([ + ToTensorVideo(), + RandomHorizontalFlipVideo(), + UCFCenterCropVideo(512), + # NormalizeVideo(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True), + transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True) + ]) + + target_video_len = 32 + frame_interval = 1 + total_frames = len(vframes) + print(total_frames) + + temporal_sample = TemporalRandomCrop(target_video_len * frame_interval) + + + # Sampling video frames + start_frame_ind, end_frame_ind = temporal_sample(total_frames) + # print(start_frame_ind) + # print(end_frame_ind) + assert end_frame_ind - start_frame_ind >= target_video_len + frame_indice = np.linspace(start_frame_ind, end_frame_ind - 1, target_video_len, dtype=int) + + select_vframes = vframes[frame_indice] + + select_vframes_trans = trans(select_vframes) + + select_vframes_trans_int = ((select_vframes_trans * 0.5 + 0.5) * 255).to(dtype=torch.uint8) + + io.write_video('./test.avi', select_vframes_trans_int.permute(0, 2, 3, 1), fps=8) + + for i in range(target_video_len): + save_image(select_vframes_trans[i], os.path.join('./test000', '%04d.png' % i), normalize=True, value_range=(-1, 1)) \ No newline at end of file diff --git a/foleycrafter/models/adapters/attention_processor.py b/foleycrafter/models/adapters/attention_processor.py new file mode 100644 index 0000000000000000000000000000000000000000..de165385bf77c483ee7844918adf1adc493e9b51 --- /dev/null +++ b/foleycrafter/models/adapters/attention_processor.py @@ -0,0 +1,653 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from typing import Union +from einops import rearrange, repeat + +from diffusers.utils import logging +from foleycrafter.models.adapters.ip_adapter import MLPProjModel + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + +class AttnProcessor(nn.Module): + r""" + Default processor for performing attention-related computations. + """ + + def __init__( + self, + hidden_size=None, + cross_attention_dim=None, + ): + super().__init__() + + def __call__( + self, + attn, + hidden_states, + encoder_hidden_states=None, + attention_mask=None, + temb=None, + ): + residual = hidden_states + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + query = attn.head_to_batch_dim(query) + key = attn.head_to_batch_dim(key) + value = attn.head_to_batch_dim(value) + + attention_probs = attn.get_attention_scores(query, key, attention_mask) + hidden_states = torch.bmm(attention_probs, value) + hidden_states = attn.batch_to_head_dim(hidden_states) + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + + +class IPAttnProcessor(nn.Module): + r""" + Attention processor for IP-Adapater. + Args: + hidden_size (`int`): + The hidden size of the attention layer. + cross_attention_dim (`int`): + The number of channels in the `encoder_hidden_states`. + scale (`float`, defaults to 1.0): + the weight scale of image prompt. + num_tokens (`int`, defaults to 4 when do ip_adapter_plus it should be 16): + The context length of the image features. + """ + + def __init__(self, hidden_size, cross_attention_dim=None, scale=1.0, num_tokens=4): + super().__init__() + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + self.scale = scale + self.num_tokens = num_tokens + + self.to_k_ip = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + self.to_v_ip = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + + def __call__( + self, + attn, + hidden_states, + encoder_hidden_states=None, + attention_mask=None, + temb=None, + ): + residual = hidden_states + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + else: + # get encoder_hidden_states, ip_hidden_states + end_pos = encoder_hidden_states.shape[1] - self.num_tokens + encoder_hidden_states, ip_hidden_states = ( + encoder_hidden_states[:, :end_pos, :], + encoder_hidden_states[:, end_pos:, :], + ) + if attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + query = attn.head_to_batch_dim(query) + key = attn.head_to_batch_dim(key) + value = attn.head_to_batch_dim(value) + + attention_probs = attn.get_attention_scores(query, key, attention_mask) + hidden_states = torch.bmm(attention_probs, value) + hidden_states = attn.batch_to_head_dim(hidden_states) + + # for ip-adapter + ip_key = self.to_k_ip(ip_hidden_states) + ip_value = self.to_v_ip(ip_hidden_states) + + ip_key = attn.head_to_batch_dim(ip_key) + ip_value = attn.head_to_batch_dim(ip_value) + + ip_attention_probs = attn.get_attention_scores(query, ip_key, None) + self.attn_map = ip_attention_probs + ip_hidden_states = torch.bmm(ip_attention_probs, ip_value) + ip_hidden_states = attn.batch_to_head_dim(ip_hidden_states) + + hidden_states = hidden_states + self.scale * ip_hidden_states + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + + +class AttnProcessor2_0(torch.nn.Module): + r""" + Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0). + """ + + def __init__( + self, + hidden_size=None, + cross_attention_dim=None, + ): + super().__init__() + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.") + + def __call__( + self, + attn, + hidden_states, + encoder_hidden_states=None, + attention_mask=None, + temb=None, + ): + residual = hidden_states + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + + if attention_mask is not None: + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + # scaled_dot_product_attention expects attention_mask shape to be + # (batch, heads, source_length, target_length) + attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1]) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + inner_dim = key.shape[-1] + head_dim = inner_dim // attn.heads + + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + hidden_states = hidden_states.to(query.dtype) + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + +class AttnProcessor2_0WithProjection(torch.nn.Module): + r""" + Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0). + """ + + def __init__( + self, + hidden_size=None, + cross_attention_dim=None, + ): + super().__init__() + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.") + self.before_proj_size = 1024 + self.after_proj_size = 768 + self.visual_proj = nn.Linear(self.before_proj_size, self.after_proj_size) + + def __call__( + self, + attn, + hidden_states, + encoder_hidden_states=None, + attention_mask=None, + temb=None, + ): + residual = hidden_states + # encoder_hidden_states = self.visual_proj(encoder_hidden_states) + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + + if attention_mask is not None: + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + # scaled_dot_product_attention expects attention_mask shape to be + # (batch, heads, source_length, target_length) + attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1]) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + inner_dim = key.shape[-1] + head_dim = inner_dim // attn.heads + + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + hidden_states = hidden_states.to(query.dtype) + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + +class IPAttnProcessor2_0(torch.nn.Module): + r""" + Attention processor for IP-Adapater for PyTorch 2.0. + Args: + hidden_size (`int`): + The hidden size of the attention layer. + cross_attention_dim (`int`): + The number of channels in the `encoder_hidden_states`. + scale (`float`, defaults to 1.0): + the weight scale of image prompt. + num_tokens (`int`, defaults to 4 when do ip_adapter_plus it should be 16): + The context length of the image features. + """ + + def __init__(self, hidden_size, cross_attention_dim=None, scale=1.0, num_tokens=4): + super().__init__() + + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.") + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + self.scale = scale + self.num_tokens = num_tokens + + self.to_k_ip = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + self.to_v_ip = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + + def __call__( + self, + attn, + hidden_states, + encoder_hidden_states=None, + attention_mask=None, + temb=None, + ): + residual = hidden_states + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + + if attention_mask is not None: + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + # scaled_dot_product_attention expects attention_mask shape to be + # (batch, heads, source_length, target_length) + attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1]) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + else: + # get encoder_hidden_states, ip_hidden_states + end_pos = encoder_hidden_states.shape[1] - self.num_tokens + encoder_hidden_states, ip_hidden_states = ( + encoder_hidden_states[:, :end_pos, :], + encoder_hidden_states[:, end_pos:, :], + ) + if attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + inner_dim = key.shape[-1] + head_dim = inner_dim // attn.heads + + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + hidden_states = hidden_states.to(query.dtype) + + # for ip-adapter + ip_key = self.to_k_ip(ip_hidden_states) + ip_value = self.to_v_ip(ip_hidden_states) + + ip_key = ip_key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + ip_value = ip_value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + ip_hidden_states = F.scaled_dot_product_attention( + query, ip_key, ip_value, attn_mask=None, dropout_p=0.0, is_causal=False + ) + with torch.no_grad(): + self.attn_map = query @ ip_key.transpose(-2, -1).softmax(dim=-1) + #print(self.attn_map.shape) + + ip_hidden_states = ip_hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + ip_hidden_states = ip_hidden_states.to(query.dtype) + + hidden_states = hidden_states + self.scale * ip_hidden_states + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + +## for controlnet +class CNAttnProcessor: + r""" + Default processor for performing attention-related computations. + """ + + def __init__(self, num_tokens=4): + self.num_tokens = num_tokens + + def __call__(self, attn, hidden_states, encoder_hidden_states=None, attention_mask=None, temb=None): + residual = hidden_states + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + else: + end_pos = encoder_hidden_states.shape[1] - self.num_tokens + encoder_hidden_states = encoder_hidden_states[:, :end_pos] # only use text + if attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + query = attn.head_to_batch_dim(query) + key = attn.head_to_batch_dim(key) + value = attn.head_to_batch_dim(value) + + attention_probs = attn.get_attention_scores(query, key, attention_mask) + hidden_states = torch.bmm(attention_probs, value) + hidden_states = attn.batch_to_head_dim(hidden_states) + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + + +class CNAttnProcessor2_0: + r""" + Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0). + """ + + def __init__(self, num_tokens=4): + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.") + self.num_tokens = num_tokens + + def __call__( + self, + attn, + hidden_states, + encoder_hidden_states=None, + attention_mask=None, + temb=None, + ): + residual = hidden_states + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + + if attention_mask is not None: + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + # scaled_dot_product_attention expects attention_mask shape to be + # (batch, heads, source_length, target_length) + attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1]) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + else: + end_pos = encoder_hidden_states.shape[1] - self.num_tokens + encoder_hidden_states = encoder_hidden_states[:, :end_pos] # only use text + if attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + inner_dim = key.shape[-1] + head_dim = inner_dim // attn.heads + + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + hidden_states = hidden_states.to(query.dtype) + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states \ No newline at end of file diff --git a/foleycrafter/models/adapters/ip_adapter.py b/foleycrafter/models/adapters/ip_adapter.py new file mode 100644 index 0000000000000000000000000000000000000000..c6bb9b5d2d63ce17add49c1a8eb2acb091212ab1 --- /dev/null +++ b/foleycrafter/models/adapters/ip_adapter.py @@ -0,0 +1,217 @@ +import torch +import torch.nn as nn + +import numpy as np + +import os +from typing import List + +from diffusers import StableDiffusionPipeline +from diffusers.pipelines.controlnet import MultiControlNetModel +from PIL import Image +from safetensors import safe_open +from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection + +from foleycrafter.models.adapters.resampler import Resampler +from foleycrafter.models.adapters.utils import is_torch2_available + +class IPAdapter(torch.nn.Module): + """IP-Adapter""" + def __init__(self, unet, image_proj_model, adapter_modules, ckpt_path=None): + super().__init__() + self.unet = unet + self.image_proj_model = image_proj_model + self.adapter_modules = adapter_modules + + if ckpt_path is not None: + self.load_from_checkpoint(ckpt_path) + + def forward(self, noisy_latents, timesteps, encoder_hidden_states, image_embeds): + ip_tokens = self.image_proj_model(image_embeds) + encoder_hidden_states = torch.cat([encoder_hidden_states, ip_tokens], dim=1) + # Predict the noise residual + noise_pred = self.unet(noisy_latents, timesteps, encoder_hidden_states).sample + return noise_pred + + def load_from_checkpoint(self, ckpt_path: str): + # Calculate original checksums + orig_ip_proj_sum = torch.sum(torch.stack([torch.sum(p) for p in self.image_proj_model.parameters()])) + orig_adapter_sum = torch.sum(torch.stack([torch.sum(p) for p in self.adapter_modules.parameters()])) + + state_dict = torch.load(ckpt_path, map_location="cpu") + + # Load state dict for image_proj_model and adapter_modules + self.image_proj_model.load_state_dict(state_dict["image_proj"], strict=True) + self.adapter_modules.load_state_dict(state_dict["ip_adapter"], strict=True) + + # Calculate new checksums + new_ip_proj_sum = torch.sum(torch.stack([torch.sum(p) for p in self.image_proj_model.parameters()])) + new_adapter_sum = torch.sum(torch.stack([torch.sum(p) for p in self.adapter_modules.parameters()])) + + # Verify if the weights have changed + assert orig_ip_proj_sum != new_ip_proj_sum, "Weights of image_proj_model did not change!" + assert orig_adapter_sum != new_adapter_sum, "Weights of adapter_modules did not change!" + + print(f"Successfully loaded weights from checkpoint {ckpt_path}") + +class VideoProjModel(torch.nn.Module): + """Projection Model""" + + def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024, clip_extra_context_tokens=1, video_frame=50): + super().__init__() + + self.cross_attention_dim = cross_attention_dim + self.clip_extra_context_tokens = clip_extra_context_tokens + self.proj = torch.nn.Linear(clip_embeddings_dim, self.clip_extra_context_tokens * cross_attention_dim) + self.norm = torch.nn.LayerNorm(cross_attention_dim) + + self.video_frame = video_frame + + def forward(self, image_embeds): + embeds = image_embeds + clip_extra_context_tokens = self.proj(embeds) + clip_extra_context_tokens = self.norm(clip_extra_context_tokens) + return clip_extra_context_tokens + +class ImageProjModel(torch.nn.Module): + """Projection Model""" + + def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024, clip_extra_context_tokens=4): + super().__init__() + + self.cross_attention_dim = cross_attention_dim + self.clip_extra_context_tokens = clip_extra_context_tokens + self.proj = torch.nn.Linear(clip_embeddings_dim, self.clip_extra_context_tokens * cross_attention_dim) + self.norm = torch.nn.LayerNorm(cross_attention_dim) + + def forward(self, image_embeds): + embeds = image_embeds + clip_extra_context_tokens = self.proj(embeds).reshape( + -1, self.clip_extra_context_tokens, self.cross_attention_dim + ) + clip_extra_context_tokens = self.norm(clip_extra_context_tokens) + return clip_extra_context_tokens + + +class MLPProjModel(torch.nn.Module): + """SD model with image prompt""" + def zero_initialize(module): + for param in module.parameters(): + param.data.zero_() + + def zero_initialize_last_layer(module): + last_layer = None + for module_name, layer in module.named_modules(): + if isinstance(layer, torch.nn.Linear): + last_layer = layer + + if last_layer is not None: + last_layer.weight.data.zero_() + last_layer.bias.data.zero_() + + def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024): + + super().__init__() + + self.proj = torch.nn.Sequential( + torch.nn.Linear(clip_embeddings_dim, clip_embeddings_dim), + torch.nn.GELU(), + torch.nn.Linear(clip_embeddings_dim, cross_attention_dim), + torch.nn.LayerNorm(cross_attention_dim) + ) + # zero initialize the last layer + # self.zero_initialize_last_layer() + + def forward(self, image_embeds): + clip_extra_context_tokens = self.proj(image_embeds) + return clip_extra_context_tokens + +class V2AMapperMLP(torch.nn.Module): + def __init__(self, cross_attention_dim=512, clip_embeddings_dim=512, mult=4): + super().__init__() + self.proj = torch.nn.Sequential( + torch.nn.Linear(clip_embeddings_dim, clip_embeddings_dim * mult), + torch.nn.GELU(), + torch.nn.Linear(clip_embeddings_dim * mult, cross_attention_dim), + torch.nn.LayerNorm(cross_attention_dim) + ) + + def forward(self, image_embeds): + clip_extra_context_tokens = self.proj(image_embeds) + return clip_extra_context_tokens + +class TimeProjModel(torch.nn.Module): + def __init__(self, positive_len, out_dim, feature_type="text-only", frame_nums:int=64): + super().__init__() + self.positive_len = positive_len + self.out_dim = out_dim + + self.position_dim = frame_nums + + if isinstance(out_dim, tuple): + out_dim = out_dim[0] + + if feature_type == "text-only": + self.linears = nn.Sequential( + nn.Linear(self.positive_len + self.position_dim, 512), + nn.SiLU(), + nn.Linear(512, 512), + nn.SiLU(), + nn.Linear(512, out_dim), + ) + self.null_positive_feature = torch.nn.Parameter(torch.zeros([self.positive_len])) + + elif feature_type == "text-image": + self.linears_text = nn.Sequential( + nn.Linear(self.positive_len + self.position_dim, 512), + nn.SiLU(), + nn.Linear(512, 512), + nn.SiLU(), + nn.Linear(512, out_dim), + ) + self.linears_image = nn.Sequential( + nn.Linear(self.positive_len + self.position_dim, 512), + nn.SiLU(), + nn.Linear(512, 512), + nn.SiLU(), + nn.Linear(512, out_dim), + ) + self.null_text_feature = torch.nn.Parameter(torch.zeros([self.positive_len])) + self.null_image_feature = torch.nn.Parameter(torch.zeros([self.positive_len])) + + # self.null_position_feature = torch.nn.Parameter(torch.zeros([self.position_dim])) + + def forward( + self, + boxes, + masks, + positive_embeddings=None, + ): + masks = masks.unsqueeze(-1) + + # # embedding position (it may includes padding as placeholder) + # xyxy_embedding = self.fourier_embedder(boxes) # B*N*4 -> B*N*C + + # # learnable null embedding + # xyxy_null = self.null_position_feature.view(1, 1, -1) + + # # replace padding with learnable null embedding + # xyxy_embedding = xyxy_embedding * masks + (1 - masks) * xyxy_null + + time_embeds = boxes + + # positionet with text only information + if positive_embeddings is not None: + # learnable null embedding + positive_null = self.null_positive_feature.view(1, 1, -1) + + # replace padding with learnable null embedding + positive_embeddings = positive_embeddings * masks + (1 - masks) * positive_null + + objs = self.linears(torch.cat([positive_embeddings, time_embeds], dim=-1)) + + # positionet with text and image infomation + else: + raise NotImplementedError + + return objs \ No newline at end of file diff --git a/foleycrafter/models/adapters/resampler.py b/foleycrafter/models/adapters/resampler.py new file mode 100644 index 0000000000000000000000000000000000000000..f18a6751cd795a607e6fe34d4f050da1aa2045c1 --- /dev/null +++ b/foleycrafter/models/adapters/resampler.py @@ -0,0 +1,158 @@ +# modified from https://github.com/mlfoundations/open_flamingo/blob/main/open_flamingo/src/helpers.py +# and https://github.com/lucidrains/imagen-pytorch/blob/main/imagen_pytorch/imagen_pytorch.py + +import math + +import torch +import torch.nn as nn +from einops import rearrange +from einops.layers.torch import Rearrange + + +# FFN +def FeedForward(dim, mult=4): + inner_dim = int(dim * mult) + return nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, inner_dim, bias=False), + nn.GELU(), + nn.Linear(inner_dim, dim, bias=False), + ) + + +def reshape_tensor(x, heads): + bs, length, width = x.shape + # (bs, length, width) --> (bs, length, n_heads, dim_per_head) + x = x.view(bs, length, heads, -1) + # (bs, length, n_heads, dim_per_head) --> (bs, n_heads, length, dim_per_head) + x = x.transpose(1, 2) + # (bs, n_heads, length, dim_per_head) --> (bs*n_heads, length, dim_per_head) + x = x.reshape(bs, heads, length, -1) + return x + + +class PerceiverAttention(nn.Module): + def __init__(self, *, dim, dim_head=64, heads=8): + super().__init__() + self.scale = dim_head**-0.5 + self.dim_head = dim_head + self.heads = heads + inner_dim = dim_head * heads + + self.norm1 = nn.LayerNorm(dim) + self.norm2 = nn.LayerNorm(dim) + + self.to_q = nn.Linear(dim, inner_dim, bias=False) + self.to_kv = nn.Linear(dim, inner_dim * 2, bias=False) + self.to_out = nn.Linear(inner_dim, dim, bias=False) + + def forward(self, x, latents): + """ + Args: + x (torch.Tensor): image features + shape (b, n1, D) + latent (torch.Tensor): latent features + shape (b, n2, D) + """ + x = self.norm1(x) + latents = self.norm2(latents) + + b, l, _ = latents.shape + + q = self.to_q(latents) + kv_input = torch.cat((x, latents), dim=-2) + k, v = self.to_kv(kv_input).chunk(2, dim=-1) + + q = reshape_tensor(q, self.heads) + k = reshape_tensor(k, self.heads) + v = reshape_tensor(v, self.heads) + + # attention + scale = 1 / math.sqrt(math.sqrt(self.dim_head)) + weight = (q * scale) @ (k * scale).transpose(-2, -1) # More stable with f16 than dividing afterwards + weight = torch.softmax(weight.float(), dim=-1).type(weight.dtype) + out = weight @ v + + out = out.permute(0, 2, 1, 3).reshape(b, l, -1) + + return self.to_out(out) + + +class Resampler(nn.Module): + def __init__( + self, + dim=1024, + depth=8, + dim_head=64, + heads=16, + num_queries=8, + embedding_dim=768, + output_dim=1024, + ff_mult=4, + max_seq_len: int = 257, # CLIP tokens + CLS token + apply_pos_emb: bool = False, + num_latents_mean_pooled: int = 0, # number of latents derived from mean pooled representation of the sequence + ): + super().__init__() + self.pos_emb = nn.Embedding(max_seq_len, embedding_dim) if apply_pos_emb else None + + self.latents = nn.Parameter(torch.randn(1, num_queries, dim) / dim**0.5) + + self.proj_in = nn.Linear(embedding_dim, dim) + + self.proj_out = nn.Linear(dim, output_dim) + self.norm_out = nn.LayerNorm(output_dim) + + self.to_latents_from_mean_pooled_seq = ( + nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, dim * num_latents_mean_pooled), + Rearrange("b (n d) -> b n d", n=num_latents_mean_pooled), + ) + if num_latents_mean_pooled > 0 + else None + ) + + self.layers = nn.ModuleList([]) + for _ in range(depth): + self.layers.append( + nn.ModuleList( + [ + PerceiverAttention(dim=dim, dim_head=dim_head, heads=heads), + FeedForward(dim=dim, mult=ff_mult), + ] + ) + ) + + def forward(self, x): + if self.pos_emb is not None: + n, device = x.shape[1], x.device + pos_emb = self.pos_emb(torch.arange(n, device=device)) + x = x + pos_emb + + latents = self.latents.repeat(x.size(0), 1, 1) + + x = self.proj_in(x) + + if self.to_latents_from_mean_pooled_seq: + meanpooled_seq = masked_mean(x, dim=1, mask=torch.ones(x.shape[:2], device=x.device, dtype=torch.bool)) + meanpooled_latents = self.to_latents_from_mean_pooled_seq(meanpooled_seq) + latents = torch.cat((meanpooled_latents, latents), dim=-2) + + for attn, ff in self.layers: + latents = attn(x, latents) + latents + latents = ff(latents) + latents + + latents = self.proj_out(latents) + return self.norm_out(latents) + + +def masked_mean(t, *, dim, mask=None): + if mask is None: + return t.mean(dim=dim) + + denom = mask.sum(dim=dim, keepdim=True) + mask = rearrange(mask, "b n -> b n 1") + masked_t = t.masked_fill(~mask, 0.0) + + return masked_t.sum(dim=dim) / denom.clamp(min=1e-5) \ No newline at end of file diff --git a/foleycrafter/models/adapters/transformer.py b/foleycrafter/models/adapters/transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..16309b4d70ca9f77b46d14cf9c2a14650833330a --- /dev/null +++ b/foleycrafter/models/adapters/transformer.py @@ -0,0 +1,327 @@ +import torch +import torch.nn as nn +import torch.utils.checkpoint + +from typing import Any, Optional, Tuple, Union + +class Attention(nn.Module): + """Multi-headed attention from 'Attention Is All You Need' paper""" + + def __init__(self, hidden_size, num_attention_heads, attention_head_dim, attention_dropout=0.0): + super().__init__() + self.embed_dim = hidden_size + self.num_heads = num_attention_heads + self.head_dim = attention_head_dim + + self.scale = self.head_dim**-0.5 + self.dropout = attention_dropout + + self.inner_dim = self.head_dim * self.num_heads + + self.k_proj = nn.Linear(self.embed_dim, self.inner_dim) + self.v_proj = nn.Linear(self.embed_dim, self.inner_dim) + self.q_proj = nn.Linear(self.embed_dim, self.inner_dim) + self.out_proj = nn.Linear(self.inner_dim, self.embed_dim) + + def _shape(self, tensor: torch.Tensor, seq_len: int, bsz: int): + return tensor.view(bsz, seq_len, self.num_heads, self.head_dim).transpose(1, 2).contiguous() + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + causal_attention_mask: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = False, + ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]: + """Input shape: Batch x Time x Channel""" + + bsz, tgt_len, embed_dim = hidden_states.size() + + # get query proj + query_states = self.q_proj(hidden_states) * self.scale + key_states = self._shape(self.k_proj(hidden_states), -1, bsz) + value_states = self._shape(self.v_proj(hidden_states), -1, bsz) + + proj_shape = (bsz * self.num_heads, -1, self.head_dim) + query_states = self._shape(query_states, tgt_len, bsz).view(*proj_shape) + key_states = key_states.view(*proj_shape) + value_states = value_states.view(*proj_shape) + + src_len = key_states.size(1) + attn_weights = torch.bmm(query_states, key_states.transpose(1, 2)) + + if attn_weights.size() != (bsz * self.num_heads, tgt_len, src_len): + raise ValueError( + f"Attention weights should be of size {(bsz * self.num_heads, tgt_len, src_len)}, but is" + f" {attn_weights.size()}" + ) + + # apply the causal_attention_mask first + if causal_attention_mask is not None: + if causal_attention_mask.size() != (bsz, 1, tgt_len, src_len): + raise ValueError( + f"Attention mask should be of size {(bsz, 1, tgt_len, src_len)}, but is" + f" {causal_attention_mask.size()}" + ) + attn_weights = attn_weights.view(bsz, self.num_heads, tgt_len, src_len) + causal_attention_mask + attn_weights = attn_weights.view(bsz * self.num_heads, tgt_len, src_len) + + if attention_mask is not None: + if attention_mask.size() != (bsz, 1, tgt_len, src_len): + raise ValueError( + f"Attention mask should be of size {(bsz, 1, tgt_len, src_len)}, but is {attention_mask.size()}" + ) + attn_weights = attn_weights.view(bsz, self.num_heads, tgt_len, src_len) + attention_mask + attn_weights = attn_weights.view(bsz * self.num_heads, tgt_len, src_len) + + attn_weights = nn.functional.softmax(attn_weights, dim=-1) + + if output_attentions: + # this operation is a bit akward, but it's required to + # make sure that attn_weights keeps its gradient. + # In order to do so, attn_weights have to reshaped + # twice and have to be reused in the following + attn_weights_reshaped = attn_weights.view(bsz, self.num_heads, tgt_len, src_len) + attn_weights = attn_weights_reshaped.view(bsz * self.num_heads, tgt_len, src_len) + else: + attn_weights_reshaped = None + + attn_probs = nn.functional.dropout(attn_weights, p=self.dropout, training=self.training) + + attn_output = torch.bmm(attn_probs, value_states) + + if attn_output.size() != (bsz * self.num_heads, tgt_len, self.head_dim): + raise ValueError( + f"`attn_output` should be of size {(bsz, self.num_heads, tgt_len, self.head_dim)}, but is" + f" {attn_output.size()}" + ) + + attn_output = attn_output.view(bsz, self.num_heads, tgt_len, self.head_dim) + attn_output = attn_output.transpose(1, 2) + attn_output = attn_output.reshape(bsz, tgt_len, self.inner_dim) + + attn_output = self.out_proj(attn_output) + + return attn_output, attn_weights_reshaped + + +class MLP(nn.Module): + def __init__(self, hidden_size, intermediate_size, mult=4): + super().__init__() + self.activation_fn = nn.SiLU() + self.fc1 = nn.Linear(hidden_size, intermediate_size * mult) + self.fc2 = nn.Linear(intermediate_size * mult, hidden_size) + + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: + hidden_states = self.fc1(hidden_states) + hidden_states = self.activation_fn(hidden_states) + hidden_states = self.fc2(hidden_states) + return hidden_states + +class Transformer(nn.Module): + def __init__(self, depth=12): + super().__init__() + self.layers = nn.ModuleList([TransformerBlock() for _ in range(depth)]) + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor=None, + causal_attention_mask: torch.Tensor=None, + output_attentions: Optional[bool] = False, + ) -> Tuple[torch.FloatTensor]: + """ + Args: + hidden_states (`torch.FloatTensor`): input to the layer of shape `(batch, seq_len, embed_dim)` + attention_mask (`torch.FloatTensor`): attention mask of size + `(batch, 1, tgt_len, src_len)` where padding elements are indicated by very large negative values. + `(config.encoder_attention_heads,)`. + output_attentions (`bool`, *optional*): + Whether or not to return the attentions tensors of all attention layers. See `attentions` under + returned tensors for more detail. + """ + for layer in self.layers: + hidden_states = layer( + hidden_states=hidden_states, + attention_mask=attention_mask, + causal_attention_mask=causal_attention_mask, + output_attentions=output_attentions, + ) + + return hidden_states + +class TransformerBlock(nn.Module): + def __init__(self, hidden_size=512, num_attention_heads=12, attention_head_dim=64, attention_dropout=0.0, dropout=0.0, eps=1e-5): + super().__init__() + self.embed_dim = hidden_size + self.self_attn = Attention(hidden_size=hidden_size, num_attention_heads=num_attention_heads, attention_head_dim=attention_head_dim) + self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=eps) + self.mlp = MLP(hidden_size=hidden_size, intermediate_size=hidden_size) + self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=eps) + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor=None, + causal_attention_mask: torch.Tensor=None, + output_attentions: Optional[bool] = False, + ) -> Tuple[torch.FloatTensor]: + """ + Args: + hidden_states (`torch.FloatTensor`): input to the layer of shape `(batch, seq_len, embed_dim)` + attention_mask (`torch.FloatTensor`): attention mask of size + `(batch, 1, tgt_len, src_len)` where padding elements are indicated by very large negative values. + `(config.encoder_attention_heads,)`. + output_attentions (`bool`, *optional*): + Whether or not to return the attentions tensors of all attention layers. See `attentions` under + returned tensors for more detail. + """ + residual = hidden_states + + hidden_states = self.layer_norm1(hidden_states) + hidden_states, attn_weights = self.self_attn( + hidden_states=hidden_states, + attention_mask=attention_mask, + causal_attention_mask=causal_attention_mask, + output_attentions=output_attentions, + ) + hidden_states = residual + hidden_states + + residual = hidden_states + hidden_states = self.layer_norm2(hidden_states) + hidden_states = self.mlp(hidden_states) + hidden_states = residual + hidden_states + + outputs = (hidden_states,) + + if output_attentions: + outputs += (attn_weights,) + + return outputs[0] + +class DiffusionTransformerBlock(nn.Module): + def __init__(self, hidden_size=512, num_attention_heads=12, attention_head_dim=64, attention_dropout=0.0, dropout=0.0, eps=1e-5): + super().__init__() + self.embed_dim = hidden_size + self.self_attn = Attention(hidden_size=hidden_size, num_attention_heads=num_attention_heads, attention_head_dim=attention_head_dim) + self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=eps) + self.mlp = MLP(hidden_size=hidden_size, intermediate_size=hidden_size) + self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=eps) + self.output_token = nn.Parameter(torch.randn(1, hidden_size)) + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor=None, + causal_attention_mask: torch.Tensor=None, + output_attentions: Optional[bool] = False, + ) -> Tuple[torch.FloatTensor]: + """ + Args: + hidden_states (`torch.FloatTensor`): input to the layer of shape `(batch, seq_len, embed_dim)` + attention_mask (`torch.FloatTensor`): attention mask of size + `(batch, 1, tgt_len, src_len)` where padding elements are indicated by very large negative values. + `(config.encoder_attention_heads,)`. + output_attentions (`bool`, *optional*): + Whether or not to return the attentions tensors of all attention layers. See `attentions` under + returned tensors for more detail. + """ + output_token = self.output_token.unsqueeze(0).repeat(hidden_states.shape[0], 1, 1) + hidden_states = torch.cat([output_token, hidden_states], dim=1) + residual = hidden_states + + hidden_states = self.layer_norm1(hidden_states) + hidden_states, attn_weights = self.self_attn( + hidden_states=hidden_states, + attention_mask=attention_mask, + causal_attention_mask=causal_attention_mask, + output_attentions=output_attentions, + ) + hidden_states = residual + hidden_states + + residual = hidden_states + hidden_states = self.layer_norm2(hidden_states) + hidden_states = self.mlp(hidden_states) + hidden_states = residual + hidden_states + + outputs = (hidden_states,) + + if output_attentions: + outputs += (attn_weights,) + + return outputs[0][:,0:1,...] + +class V2AMapperMLP(nn.Module): + def __init__(self, input_dim=512, output_dim=512, expansion_rate=4): + super().__init__() + self.linear = nn.Linear(input_dim, input_dim * expansion_rate) + self.silu = nn.SiLU() + self.layer_norm = nn.LayerNorm(input_dim * expansion_rate) + self.linear2 = nn.Linear(input_dim * expansion_rate, output_dim) + + def forward(self, x): + + x = self.linear(x) + x = self.silu(x) + x = self.layer_norm(x) + x = self.linear2(x) + + return x + +class ImageProjModel(torch.nn.Module): + """Projection Model""" + + def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024, clip_extra_context_tokens=4): + super().__init__() + + self.cross_attention_dim = cross_attention_dim + self.clip_extra_context_tokens = clip_extra_context_tokens + self.proj = torch.nn.Linear(clip_embeddings_dim, self.clip_extra_context_tokens * cross_attention_dim) + self.norm = torch.nn.LayerNorm(cross_attention_dim) + + self.zero_initialize_last_layer() + + def zero_initialize_last_layer(module): + last_layer = None + for module_name, layer in module.named_modules(): + if isinstance(layer, torch.nn.Linear): + last_layer = layer + + if last_layer is not None: + last_layer.weight.data.zero_() + last_layer.bias.data.zero_() + + def forward(self, image_embeds): + embeds = image_embeds + clip_extra_context_tokens = self.proj(embeds).reshape( + -1, self.clip_extra_context_tokens, self.cross_attention_dim + ) + clip_extra_context_tokens = self.norm(clip_extra_context_tokens) + return clip_extra_context_tokens + +class VisionAudioAdapter(torch.nn.Module): + def __init__( + self, + embedding_size=768, + expand_dim=4, + token_num=4, + ): + super().__init__() + + self.mapper = V2AMapperMLP( + embedding_size, + embedding_size, + expansion_rate=expand_dim, + ) + + self.proj = ImageProjModel( + cross_attention_dim=embedding_size, + clip_embeddings_dim=embedding_size, + clip_extra_context_tokens=token_num, + ) + + def forward(self, image_embeds): + image_embeds = self.mapper(image_embeds) + image_embeds = self.proj(image_embeds) + return image_embeds + + \ No newline at end of file diff --git a/foleycrafter/models/adapters/utils.py b/foleycrafter/models/adapters/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..edd7879590a495d11f11d7a1265445705d8bfb72 --- /dev/null +++ b/foleycrafter/models/adapters/utils.py @@ -0,0 +1,81 @@ +import torch +import torch.nn.functional as F +import numpy as np +from PIL import Image + +attn_maps = {} +def hook_fn(name): + def forward_hook(module, input, output): + if hasattr(module.processor, "attn_map"): + attn_maps[name] = module.processor.attn_map + del module.processor.attn_map + + return forward_hook + +def register_cross_attention_hook(unet): + for name, module in unet.named_modules(): + if name.split('.')[-1].startswith('attn2'): + module.register_forward_hook(hook_fn(name)) + + return unet + +def upscale(attn_map, target_size): + attn_map = torch.mean(attn_map, dim=0) + attn_map = attn_map.permute(1,0) + temp_size = None + + for i in range(0,5): + scale = 2 ** i + if ( target_size[0] // scale ) * ( target_size[1] // scale) == attn_map.shape[1]*64: + temp_size = (target_size[0]//(scale*8), target_size[1]//(scale*8)) + break + + assert temp_size is not None, "temp_size cannot is None" + + attn_map = attn_map.view(attn_map.shape[0], *temp_size) + + attn_map = F.interpolate( + attn_map.unsqueeze(0).to(dtype=torch.float32), + size=target_size, + mode='bilinear', + align_corners=False + )[0] + + attn_map = torch.softmax(attn_map, dim=0) + return attn_map +def get_net_attn_map(image_size, batch_size=2, instance_or_negative=False, detach=True): + + idx = 0 if instance_or_negative else 1 + net_attn_maps = [] + + for name, attn_map in attn_maps.items(): + attn_map = attn_map.cpu() if detach else attn_map + attn_map = torch.chunk(attn_map, batch_size)[idx].squeeze() + attn_map = upscale(attn_map, image_size) + net_attn_maps.append(attn_map) + + net_attn_maps = torch.mean(torch.stack(net_attn_maps,dim=0),dim=0) + + return net_attn_maps + +def attnmaps2images(net_attn_maps): + + #total_attn_scores = 0 + images = [] + + for attn_map in net_attn_maps: + attn_map = attn_map.cpu().numpy() + #total_attn_scores += attn_map.mean().item() + + normalized_attn_map = (attn_map - np.min(attn_map)) / (np.max(attn_map) - np.min(attn_map)) * 255 + normalized_attn_map = normalized_attn_map.astype(np.uint8) + #print("norm: ", normalized_attn_map.shape) + image = Image.fromarray(normalized_attn_map) + + #image = fix_save_attn_map(attn_map) + images.append(image) + + #print(total_attn_scores) + return images +def is_torch2_available(): + return hasattr(F, "scaled_dot_product_attention") \ No newline at end of file diff --git a/foleycrafter/models/auffusion/attention.py b/foleycrafter/models/auffusion/attention.py new file mode 100644 index 0000000000000000000000000000000000000000..fc362a8718b8f79f7d1a875cf56cf70e8da17b6c --- /dev/null +++ b/foleycrafter/models/auffusion/attention.py @@ -0,0 +1,669 @@ +# Copyright 2023 The HuggingFace 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 +# limitations under the License. +from typing import Any, Dict, Optional + +import torch +import torch.nn.functional as F +from torch import nn + +from diffusers.utils import USE_PEFT_BACKEND +from diffusers.utils.torch_utils import maybe_allow_in_graph +from diffusers.models.activations import GEGLU, GELU, ApproximateGELU +from diffusers.models.embeddings import SinusoidalPositionalEmbedding +from diffusers.models.lora import LoRACompatibleLinear +from diffusers.models.normalization import\ + AdaLayerNorm, AdaLayerNormContinuous, AdaLayerNormZero, RMSNorm + +from foleycrafter.models.auffusion.attention_processor import Attention + +def _chunked_feed_forward( + ff: nn.Module, hidden_states: torch.Tensor, chunk_dim: int, chunk_size: int, lora_scale: Optional[float] = None +): + # "feed_forward_chunk_size" can be used to save memory + if hidden_states.shape[chunk_dim] % chunk_size != 0: + raise ValueError( + f"`hidden_states` dimension to be chunked: {hidden_states.shape[chunk_dim]} has to be divisible by chunk size: {chunk_size}. Make sure to set an appropriate `chunk_size` when calling `unet.enable_forward_chunking`." + ) + + num_chunks = hidden_states.shape[chunk_dim] // chunk_size + if lora_scale is None: + ff_output = torch.cat( + [ff(hid_slice) for hid_slice in hidden_states.chunk(num_chunks, dim=chunk_dim)], + dim=chunk_dim, + ) + else: + # TOOD(Patrick): LoRA scale can be removed once PEFT refactor is complete + ff_output = torch.cat( + [ff(hid_slice, scale=lora_scale) for hid_slice in hidden_states.chunk(num_chunks, dim=chunk_dim)], + dim=chunk_dim, + ) + + return ff_output + + +@maybe_allow_in_graph +class GatedSelfAttentionDense(nn.Module): + r""" + A gated self-attention dense layer that combines visual features and object features. + + Parameters: + query_dim (`int`): The number of channels in the query. + context_dim (`int`): The number of channels in the context. + n_heads (`int`): The number of heads to use for attention. + d_head (`int`): The number of channels in each head. + """ + + def __init__(self, query_dim: int, context_dim: int, n_heads: int, d_head: int): + super().__init__() + + # we need a linear projection since we need cat visual feature and obj feature + self.linear = nn.Linear(context_dim, query_dim) + + self.attn = Attention(query_dim=query_dim, heads=n_heads, dim_head=d_head) + self.ff = FeedForward(query_dim, activation_fn="geglu") + + self.norm1 = nn.LayerNorm(query_dim) + self.norm2 = nn.LayerNorm(query_dim) + + self.register_parameter("alpha_attn", nn.Parameter(torch.tensor(0.0))) + self.register_parameter("alpha_dense", nn.Parameter(torch.tensor(0.0))) + + self.enabled = True + + def forward(self, x: torch.Tensor, objs: torch.Tensor) -> torch.Tensor: + if not self.enabled: + return x + + n_visual = x.shape[1] + objs = self.linear(objs) + + x = x + self.alpha_attn.tanh() * self.attn(self.norm1(torch.cat([x, objs], dim=1)))[:, :n_visual, :] + x = x + self.alpha_dense.tanh() * self.ff(self.norm2(x)) + + return x + + +@maybe_allow_in_graph +class BasicTransformerBlock(nn.Module): + r""" + A basic Transformer block. + + Parameters: + dim (`int`): The number of channels in the input and output. + num_attention_heads (`int`): The number of heads to use for multi-head attention. + attention_head_dim (`int`): The number of channels in each head. + dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use. + cross_attention_dim (`int`, *optional*): The size of the encoder_hidden_states vector for cross attention. + activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward. + num_embeds_ada_norm (: + obj: `int`, *optional*): The number of diffusion steps used during training. See `Transformer2DModel`. + attention_bias (: + obj: `bool`, *optional*, defaults to `False`): Configure if the attentions should contain a bias parameter. + only_cross_attention (`bool`, *optional*): + Whether to use only cross-attention layers. In this case two cross attention layers are used. + double_self_attention (`bool`, *optional*): + Whether to use two self-attention layers. In this case no cross attention layers are used. + upcast_attention (`bool`, *optional*): + Whether to upcast the attention computation to float32. This is useful for mixed precision training. + norm_elementwise_affine (`bool`, *optional*, defaults to `True`): + Whether to use learnable elementwise affine parameters for normalization. + norm_type (`str`, *optional*, defaults to `"layer_norm"`): + The normalization layer to use. Can be `"layer_norm"`, `"ada_norm"` or `"ada_norm_zero"`. + final_dropout (`bool` *optional*, defaults to False): + Whether to apply a final dropout after the last feed-forward layer. + attention_type (`str`, *optional*, defaults to `"default"`): + The type of attention to use. Can be `"default"` or `"gated"` or `"gated-text-image"`. + positional_embeddings (`str`, *optional*, defaults to `None`): + The type of positional embeddings to apply to. + num_positional_embeddings (`int`, *optional*, defaults to `None`): + The maximum number of positional embeddings to apply. + """ + + def __init__( + self, + dim: int, + num_attention_heads: int, + attention_head_dim: int, + dropout=0.0, + cross_attention_dim: Optional[int] = None, + activation_fn: str = "geglu", + num_embeds_ada_norm: Optional[int] = None, + attention_bias: bool = False, + only_cross_attention: bool = False, + double_self_attention: bool = False, + upcast_attention: bool = False, + norm_elementwise_affine: bool = True, + norm_type: str = "layer_norm", # 'layer_norm', 'ada_norm', 'ada_norm_zero', 'ada_norm_single' + norm_eps: float = 1e-5, + final_dropout: bool = False, + attention_type: str = "default", + positional_embeddings: Optional[str] = None, + num_positional_embeddings: Optional[int] = None, + ada_norm_continous_conditioning_embedding_dim: Optional[int] = None, + ada_norm_bias: Optional[int] = None, + ff_inner_dim: Optional[int] = None, + ff_bias: bool = True, + attention_out_bias: bool = True, + ): + super().__init__() + self.only_cross_attention = only_cross_attention + + self.use_ada_layer_norm_zero = (num_embeds_ada_norm is not None) and norm_type == "ada_norm_zero" + self.use_ada_layer_norm = (num_embeds_ada_norm is not None) and norm_type == "ada_norm" + self.use_ada_layer_norm_single = norm_type == "ada_norm_single" + self.use_layer_norm = norm_type == "layer_norm" + self.use_ada_layer_norm_continuous = norm_type == "ada_norm_continuous" + + if norm_type in ("ada_norm", "ada_norm_zero") and num_embeds_ada_norm is None: + raise ValueError( + f"`norm_type` is set to {norm_type}, but `num_embeds_ada_norm` is not defined. Please make sure to" + f" define `num_embeds_ada_norm` if setting `norm_type` to {norm_type}." + ) + + if positional_embeddings and (num_positional_embeddings is None): + raise ValueError( + "If `positional_embedding` type is defined, `num_positition_embeddings` must also be defined." + ) + + if positional_embeddings == "sinusoidal": + self.pos_embed = SinusoidalPositionalEmbedding(dim, max_seq_length=num_positional_embeddings) + else: + self.pos_embed = None + + # Define 3 blocks. Each block has its own normalization layer. + # 1. Self-Attn + if self.use_ada_layer_norm: + self.norm1 = AdaLayerNorm(dim, num_embeds_ada_norm) + elif self.use_ada_layer_norm_zero: + self.norm1 = AdaLayerNormZero(dim, num_embeds_ada_norm) + elif self.use_ada_layer_norm_continuous: + self.norm1 = AdaLayerNormContinuous( + dim, + ada_norm_continous_conditioning_embedding_dim, + norm_elementwise_affine, + norm_eps, + ada_norm_bias, + "rms_norm", + ) + else: + self.norm1 = nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine, eps=norm_eps) + + self.attn1 = Attention( + query_dim=dim, + heads=num_attention_heads, + dim_head=attention_head_dim, + dropout=dropout, + bias=attention_bias, + cross_attention_dim=cross_attention_dim if (only_cross_attention and not double_self_attention) else None, + upcast_attention=upcast_attention, + out_bias=attention_out_bias, + ) + + # 2. Cross-Attn + if cross_attention_dim is not None or double_self_attention: + # We currently only use AdaLayerNormZero for self attention where there will only be one attention block. + # I.e. the number of returned modulation chunks from AdaLayerZero would not make sense if returned during + # the second cross attention block. + if self.use_ada_layer_norm: + self.norm2 = AdaLayerNorm(dim, num_embeds_ada_norm) + elif self.use_ada_layer_norm_continuous: + self.norm2 = AdaLayerNormContinuous( + dim, + ada_norm_continous_conditioning_embedding_dim, + norm_elementwise_affine, + norm_eps, + ada_norm_bias, + "rms_norm", + ) + else: + self.norm2 = nn.LayerNorm(dim, norm_eps, norm_elementwise_affine) + + self.attn2 = Attention( + query_dim=dim, + cross_attention_dim=cross_attention_dim if not double_self_attention else None, + heads=num_attention_heads, + dim_head=attention_head_dim, + dropout=dropout, + bias=attention_bias, + upcast_attention=upcast_attention, + out_bias=attention_out_bias, + ) # is self-attn if encoder_hidden_states is none + else: + self.norm2 = None + self.attn2 = None + + # 3. Feed-forward + if self.use_ada_layer_norm_continuous: + self.norm3 = AdaLayerNormContinuous( + dim, + ada_norm_continous_conditioning_embedding_dim, + norm_elementwise_affine, + norm_eps, + ada_norm_bias, + "layer_norm", + ) + elif not self.use_ada_layer_norm_single: + self.norm3 = nn.LayerNorm(dim, norm_eps, norm_elementwise_affine) + + self.ff = FeedForward( + dim, + dropout=dropout, + activation_fn=activation_fn, + final_dropout=final_dropout, + inner_dim=ff_inner_dim, + bias=ff_bias, + ) + + # 4. Fuser + if attention_type == "gated" or attention_type == "gated-text-image": + self.fuser = GatedSelfAttentionDense(dim, cross_attention_dim, num_attention_heads, attention_head_dim) + + # 5. Scale-shift for PixArt-Alpha. + if self.use_ada_layer_norm_single: + self.scale_shift_table = nn.Parameter(torch.randn(6, dim) / dim**0.5) + + # let chunk size default to None + self._chunk_size = None + self._chunk_dim = 0 + + def set_chunk_feed_forward(self, chunk_size: Optional[int], dim: int = 0): + # Sets chunk feed-forward + self._chunk_size = chunk_size + self._chunk_dim = dim + + def forward( + self, + hidden_states: torch.FloatTensor, + attention_mask: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + timestep: Optional[torch.LongTensor] = None, + cross_attention_kwargs: Dict[str, Any] = None, + class_labels: Optional[torch.LongTensor] = None, + added_cond_kwargs: Optional[Dict[str, torch.Tensor]] = None, + ) -> torch.FloatTensor: + # Notice that normalization is always applied before the real computation in the following blocks. + # 0. Self-Attention + batch_size = hidden_states.shape[0] + + if self.use_ada_layer_norm: + norm_hidden_states = self.norm1(hidden_states, timestep) + elif self.use_ada_layer_norm_zero: + norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1( + hidden_states, timestep, class_labels, hidden_dtype=hidden_states.dtype + ) + elif self.use_layer_norm: + norm_hidden_states = self.norm1(hidden_states) + elif self.use_ada_layer_norm_continuous: + norm_hidden_states = self.norm1(hidden_states, added_cond_kwargs["pooled_text_emb"]) + elif self.use_ada_layer_norm_single: + shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = ( + self.scale_shift_table[None] + timestep.reshape(batch_size, 6, -1) + ).chunk(6, dim=1) + norm_hidden_states = self.norm1(hidden_states) + norm_hidden_states = norm_hidden_states * (1 + scale_msa) + shift_msa + norm_hidden_states = norm_hidden_states.squeeze(1) + else: + raise ValueError("Incorrect norm used") + + if self.pos_embed is not None: + norm_hidden_states = self.pos_embed(norm_hidden_states) + + # 1. Retrieve lora scale. + lora_scale = cross_attention_kwargs.get("scale", 1.0) if cross_attention_kwargs is not None else 1.0 + + # 2. Prepare GLIGEN inputs + cross_attention_kwargs = cross_attention_kwargs.copy() if cross_attention_kwargs is not None else {} + gligen_kwargs = cross_attention_kwargs.pop("gligen", None) + + attn_output = self.attn1( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states if self.only_cross_attention else None, + attention_mask=attention_mask, + **cross_attention_kwargs, + ) + if self.use_ada_layer_norm_zero: + attn_output = gate_msa.unsqueeze(1) * attn_output + elif self.use_ada_layer_norm_single: + attn_output = gate_msa * attn_output + + hidden_states = attn_output + hidden_states + if hidden_states.ndim == 4: + hidden_states = hidden_states.squeeze(1) + + # 2.5 GLIGEN Control + if gligen_kwargs is not None: + hidden_states = self.fuser(hidden_states, gligen_kwargs["objs"]) + + # 3. Cross-Attention + if self.attn2 is not None: + if self.use_ada_layer_norm: + norm_hidden_states = self.norm2(hidden_states, timestep) + elif self.use_ada_layer_norm_zero or self.use_layer_norm: + norm_hidden_states = self.norm2(hidden_states) + elif self.use_ada_layer_norm_single: + # For PixArt norm2 isn't applied here: + # https://github.com/PixArt-alpha/PixArt-alpha/blob/0f55e922376d8b797edd44d25d0e7464b260dcab/diffusion/model/nets/PixArtMS.py#L70C1-L76C103 + norm_hidden_states = hidden_states + elif self.use_ada_layer_norm_continuous: + norm_hidden_states = self.norm2(hidden_states, added_cond_kwargs["pooled_text_emb"]) + else: + raise ValueError("Incorrect norm") + + if self.pos_embed is not None and self.use_ada_layer_norm_single is False: + norm_hidden_states = self.pos_embed(norm_hidden_states) + + attn_output = self.attn2( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=encoder_attention_mask, + **cross_attention_kwargs, + ) + hidden_states = attn_output + hidden_states + + # 4. Feed-forward + if self.use_ada_layer_norm_continuous: + norm_hidden_states = self.norm3(hidden_states, added_cond_kwargs["pooled_text_emb"]) + elif not self.use_ada_layer_norm_single: + norm_hidden_states = self.norm3(hidden_states) + + if self.use_ada_layer_norm_zero: + norm_hidden_states = norm_hidden_states * (1 + scale_mlp[:, None]) + shift_mlp[:, None] + + if self.use_ada_layer_norm_single: + norm_hidden_states = self.norm2(hidden_states) + norm_hidden_states = norm_hidden_states * (1 + scale_mlp) + shift_mlp + + if self._chunk_size is not None: + # "feed_forward_chunk_size" can be used to save memory + ff_output = _chunked_feed_forward( + self.ff, norm_hidden_states, self._chunk_dim, self._chunk_size, lora_scale=lora_scale + ) + else: + ff_output = self.ff(norm_hidden_states, scale=lora_scale) + + if self.use_ada_layer_norm_zero: + ff_output = gate_mlp.unsqueeze(1) * ff_output + elif self.use_ada_layer_norm_single: + ff_output = gate_mlp * ff_output + + hidden_states = ff_output + hidden_states + if hidden_states.ndim == 4: + hidden_states = hidden_states.squeeze(1) + + return hidden_states + + +@maybe_allow_in_graph +class TemporalBasicTransformerBlock(nn.Module): + r""" + A basic Transformer block for video like data. + + Parameters: + dim (`int`): The number of channels in the input and output. + time_mix_inner_dim (`int`): The number of channels for temporal attention. + num_attention_heads (`int`): The number of heads to use for multi-head attention. + attention_head_dim (`int`): The number of channels in each head. + cross_attention_dim (`int`, *optional*): The size of the encoder_hidden_states vector for cross attention. + """ + + def __init__( + self, + dim: int, + time_mix_inner_dim: int, + num_attention_heads: int, + attention_head_dim: int, + cross_attention_dim: Optional[int] = None, + ): + super().__init__() + self.is_res = dim == time_mix_inner_dim + + self.norm_in = nn.LayerNorm(dim) + + # Define 3 blocks. Each block has its own normalization layer. + # 1. Self-Attn + self.norm_in = nn.LayerNorm(dim) + self.ff_in = FeedForward( + dim, + dim_out=time_mix_inner_dim, + activation_fn="geglu", + ) + + self.norm1 = nn.LayerNorm(time_mix_inner_dim) + self.attn1 = Attention( + query_dim=time_mix_inner_dim, + heads=num_attention_heads, + dim_head=attention_head_dim, + cross_attention_dim=None, + ) + + # 2. Cross-Attn + if cross_attention_dim is not None: + # We currently only use AdaLayerNormZero for self attention where there will only be one attention block. + # I.e. the number of returned modulation chunks from AdaLayerZero would not make sense if returned during + # the second cross attention block. + self.norm2 = nn.LayerNorm(time_mix_inner_dim) + self.attn2 = Attention( + query_dim=time_mix_inner_dim, + cross_attention_dim=cross_attention_dim, + heads=num_attention_heads, + dim_head=attention_head_dim, + ) # is self-attn if encoder_hidden_states is none + else: + self.norm2 = None + self.attn2 = None + + # 3. Feed-forward + self.norm3 = nn.LayerNorm(time_mix_inner_dim) + self.ff = FeedForward(time_mix_inner_dim, activation_fn="geglu") + + # let chunk size default to None + self._chunk_size = None + self._chunk_dim = None + + def set_chunk_feed_forward(self, chunk_size: Optional[int], **kwargs): + # Sets chunk feed-forward + self._chunk_size = chunk_size + # chunk dim should be hardcoded to 1 to have better speed vs. memory trade-off + self._chunk_dim = 1 + + def forward( + self, + hidden_states: torch.FloatTensor, + num_frames: int, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + # Notice that normalization is always applied before the real computation in the following blocks. + # 0. Self-Attention + batch_size = hidden_states.shape[0] + + batch_frames, seq_length, channels = hidden_states.shape + batch_size = batch_frames // num_frames + + hidden_states = hidden_states[None, :].reshape(batch_size, num_frames, seq_length, channels) + hidden_states = hidden_states.permute(0, 2, 1, 3) + hidden_states = hidden_states.reshape(batch_size * seq_length, num_frames, channels) + + residual = hidden_states + hidden_states = self.norm_in(hidden_states) + + if self._chunk_size is not None: + hidden_states = _chunked_feed_forward(self.ff_in, hidden_states, self._chunk_dim, self._chunk_size) + else: + hidden_states = self.ff_in(hidden_states) + + if self.is_res: + hidden_states = hidden_states + residual + + norm_hidden_states = self.norm1(hidden_states) + attn_output = self.attn1(norm_hidden_states, encoder_hidden_states=None) + hidden_states = attn_output + hidden_states + + # 3. Cross-Attention + if self.attn2 is not None: + norm_hidden_states = self.norm2(hidden_states) + attn_output = self.attn2(norm_hidden_states, encoder_hidden_states=encoder_hidden_states) + hidden_states = attn_output + hidden_states + + # 4. Feed-forward + norm_hidden_states = self.norm3(hidden_states) + + if self._chunk_size is not None: + ff_output = _chunked_feed_forward(self.ff, norm_hidden_states, self._chunk_dim, self._chunk_size) + else: + ff_output = self.ff(norm_hidden_states) + + if self.is_res: + hidden_states = ff_output + hidden_states + else: + hidden_states = ff_output + + hidden_states = hidden_states[None, :].reshape(batch_size, seq_length, num_frames, channels) + hidden_states = hidden_states.permute(0, 2, 1, 3) + hidden_states = hidden_states.reshape(batch_size * num_frames, seq_length, channels) + + return hidden_states + + +class SkipFFTransformerBlock(nn.Module): + def __init__( + self, + dim: int, + num_attention_heads: int, + attention_head_dim: int, + kv_input_dim: int, + kv_input_dim_proj_use_bias: bool, + dropout=0.0, + cross_attention_dim: Optional[int] = None, + attention_bias: bool = False, + attention_out_bias: bool = True, + ): + super().__init__() + if kv_input_dim != dim: + self.kv_mapper = nn.Linear(kv_input_dim, dim, kv_input_dim_proj_use_bias) + else: + self.kv_mapper = None + + self.norm1 = RMSNorm(dim, 1e-06) + + self.attn1 = Attention( + query_dim=dim, + heads=num_attention_heads, + dim_head=attention_head_dim, + dropout=dropout, + bias=attention_bias, + cross_attention_dim=cross_attention_dim, + out_bias=attention_out_bias, + ) + + self.norm2 = RMSNorm(dim, 1e-06) + + self.attn2 = Attention( + query_dim=dim, + cross_attention_dim=cross_attention_dim, + heads=num_attention_heads, + dim_head=attention_head_dim, + dropout=dropout, + bias=attention_bias, + out_bias=attention_out_bias, + ) + + def forward(self, hidden_states, encoder_hidden_states, cross_attention_kwargs): + cross_attention_kwargs = cross_attention_kwargs.copy() if cross_attention_kwargs is not None else {} + + if self.kv_mapper is not None: + encoder_hidden_states = self.kv_mapper(F.silu(encoder_hidden_states)) + + norm_hidden_states = self.norm1(hidden_states) + + attn_output = self.attn1( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states, + **cross_attention_kwargs, + ) + + hidden_states = attn_output + hidden_states + + norm_hidden_states = self.norm2(hidden_states) + + attn_output = self.attn2( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states, + **cross_attention_kwargs, + ) + + hidden_states = attn_output + hidden_states + + return hidden_states + + +class FeedForward(nn.Module): + r""" + A feed-forward layer. + + Parameters: + dim (`int`): The number of channels in the input. + dim_out (`int`, *optional*): The number of channels in the output. If not given, defaults to `dim`. + mult (`int`, *optional*, defaults to 4): The multiplier to use for the hidden dimension. + dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use. + activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward. + final_dropout (`bool` *optional*, defaults to False): Apply a final dropout. + bias (`bool`, defaults to True): Whether to use a bias in the linear layer. + """ + + def __init__( + self, + dim: int, + dim_out: Optional[int] = None, + mult: int = 4, + dropout: float = 0.0, + activation_fn: str = "geglu", + final_dropout: bool = False, + inner_dim=None, + bias: bool = True, + ): + super().__init__() + if inner_dim is None: + inner_dim = int(dim * mult) + dim_out = dim_out if dim_out is not None else dim + linear_cls = LoRACompatibleLinear if not USE_PEFT_BACKEND else nn.Linear + + if activation_fn == "gelu": + act_fn = GELU(dim, inner_dim, bias=bias) + if activation_fn == "gelu-approximate": + act_fn = GELU(dim, inner_dim, approximate="tanh", bias=bias) + elif activation_fn == "geglu": + act_fn = GEGLU(dim, inner_dim, bias=bias) + elif activation_fn == "geglu-approximate": + act_fn = ApproximateGELU(dim, inner_dim, bias=bias) + + self.net = nn.ModuleList([]) + # project in + self.net.append(act_fn) + # project dropout + self.net.append(nn.Dropout(dropout)) + # project out + self.net.append(linear_cls(inner_dim, dim_out, bias=bias)) + # FF as used in Vision Transformer, MLP-Mixer, etc. have a final dropout + if final_dropout: + self.net.append(nn.Dropout(dropout)) + + def forward(self, hidden_states: torch.Tensor, scale: float = 1.0) -> torch.Tensor: + compatible_cls = (GEGLU,) if USE_PEFT_BACKEND else (GEGLU, LoRACompatibleLinear) + for module in self.net: + if isinstance(module, compatible_cls): + hidden_states = module(hidden_states, scale) + else: + hidden_states = module(hidden_states) + return hidden_states \ No newline at end of file diff --git a/foleycrafter/models/auffusion/attention_processor.py b/foleycrafter/models/auffusion/attention_processor.py new file mode 100644 index 0000000000000000000000000000000000000000..c46ac9a8773a2a535a758e9cf5eddc9c73f04df6 --- /dev/null +++ b/foleycrafter/models/auffusion/attention_processor.py @@ -0,0 +1,2682 @@ +# Copyright 2023 The HuggingFace 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 +# limitations under the License. +from importlib import import_module +from typing import Callable, Optional, Union, List + +import torch +import torch.nn.functional as F +from torch import nn +import math + +from einops import rearrange + +from diffusers.utils import USE_PEFT_BACKEND, deprecate, logging +from diffusers.utils.import_utils import is_xformers_available +from diffusers.utils.torch_utils import maybe_allow_in_graph +from diffusers.models.lora import LoRACompatibleLinear, LoRALinearLayer + + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + +if is_xformers_available(): + import xformers + import xformers.ops +else: + xformers = None + + +@maybe_allow_in_graph +class Attention(nn.Module): + r""" + A cross attention layer. + + Parameters: + query_dim (`int`): + The number of channels in the query. + cross_attention_dim (`int`, *optional*): + The number of channels in the encoder_hidden_states. If not given, defaults to `query_dim`. + heads (`int`, *optional*, defaults to 8): + The number of heads to use for multi-head attention. + dim_head (`int`, *optional*, defaults to 64): + The number of channels in each head. + dropout (`float`, *optional*, defaults to 0.0): + The dropout probability to use. + bias (`bool`, *optional*, defaults to False): + Set to `True` for the query, key, and value linear layers to contain a bias parameter. + upcast_attention (`bool`, *optional*, defaults to False): + Set to `True` to upcast the attention computation to `float32`. + upcast_softmax (`bool`, *optional*, defaults to False): + Set to `True` to upcast the softmax computation to `float32`. + cross_attention_norm (`str`, *optional*, defaults to `None`): + The type of normalization to use for the cross attention. Can be `None`, `layer_norm`, or `group_norm`. + cross_attention_norm_num_groups (`int`, *optional*, defaults to 32): + The number of groups to use for the group norm in the cross attention. + added_kv_proj_dim (`int`, *optional*, defaults to `None`): + The number of channels to use for the added key and value projections. If `None`, no projection is used. + norm_num_groups (`int`, *optional*, defaults to `None`): + The number of groups to use for the group norm in the attention. + spatial_norm_dim (`int`, *optional*, defaults to `None`): + The number of channels to use for the spatial normalization. + out_bias (`bool`, *optional*, defaults to `True`): + Set to `True` to use a bias in the output linear layer. + scale_qk (`bool`, *optional*, defaults to `True`): + Set to `True` to scale the query and key by `1 / sqrt(dim_head)`. + only_cross_attention (`bool`, *optional*, defaults to `False`): + Set to `True` to only use cross attention and not added_kv_proj_dim. Can only be set to `True` if + `added_kv_proj_dim` is not `None`. + eps (`float`, *optional*, defaults to 1e-5): + An additional value added to the denominator in group normalization that is used for numerical stability. + rescale_output_factor (`float`, *optional*, defaults to 1.0): + A factor to rescale the output by dividing it with this value. + residual_connection (`bool`, *optional*, defaults to `False`): + Set to `True` to add the residual connection to the output. + _from_deprecated_attn_block (`bool`, *optional*, defaults to `False`): + Set to `True` if the attention block is loaded from a deprecated state dict. + processor (`AttnProcessor`, *optional*, defaults to `None`): + The attention processor to use. If `None`, defaults to `AttnProcessor2_0` if `torch 2.x` is used and + `AttnProcessor` otherwise. + """ + + def __init__( + self, + query_dim: int, + cross_attention_dim: Optional[int] = None, + heads: int = 8, + dim_head: int = 64, + dropout: float = 0.0, + bias: bool = False, + upcast_attention: bool = False, + upcast_softmax: bool = False, + cross_attention_norm: Optional[str] = None, + cross_attention_norm_num_groups: int = 32, + added_kv_proj_dim: Optional[int] = None, + norm_num_groups: Optional[int] = None, + spatial_norm_dim: Optional[int] = None, + out_bias: bool = True, + scale_qk: bool = True, + only_cross_attention: bool = False, + eps: float = 1e-5, + rescale_output_factor: float = 1.0, + residual_connection: bool = False, + _from_deprecated_attn_block: bool = False, + processor: Optional["AttnProcessor"] = None, + out_dim: int = None, + ): + super().__init__() + self.inner_dim = out_dim if out_dim is not None else dim_head * heads + self.query_dim = query_dim + self.cross_attention_dim = cross_attention_dim if cross_attention_dim is not None else query_dim + self.upcast_attention = upcast_attention + self.upcast_softmax = upcast_softmax + self.rescale_output_factor = rescale_output_factor + self.residual_connection = residual_connection + self.dropout = dropout + self.fused_projections = False + self.out_dim = out_dim if out_dim is not None else query_dim + + # we make use of this private variable to know whether this class is loaded + # with an deprecated state dict so that we can convert it on the fly + self._from_deprecated_attn_block = _from_deprecated_attn_block + + self.scale_qk = scale_qk + self.scale = dim_head**-0.5 if self.scale_qk else 1.0 + + self.heads = out_dim // dim_head if out_dim is not None else heads + # for slice_size > 0 the attention score computation + # is split across the batch axis to save memory + # You can set slice_size with `set_attention_slice` + self.sliceable_head_dim = heads + + self.added_kv_proj_dim = added_kv_proj_dim + self.only_cross_attention = only_cross_attention + + if self.added_kv_proj_dim is None and self.only_cross_attention: + raise ValueError( + "`only_cross_attention` can only be set to True if `added_kv_proj_dim` is not None. Make sure to set either `only_cross_attention=False` or define `added_kv_proj_dim`." + ) + + if norm_num_groups is not None: + self.group_norm = nn.GroupNorm(num_channels=query_dim, num_groups=norm_num_groups, eps=eps, affine=True) + else: + self.group_norm = None + + if spatial_norm_dim is not None: + self.spatial_norm = SpatialNorm(f_channels=query_dim, zq_channels=spatial_norm_dim) + else: + self.spatial_norm = None + + if cross_attention_norm is None: + self.norm_cross = None + elif cross_attention_norm == "layer_norm": + self.norm_cross = nn.LayerNorm(self.cross_attention_dim) + elif cross_attention_norm == "group_norm": + if self.added_kv_proj_dim is not None: + # The given `encoder_hidden_states` are initially of shape + # (batch_size, seq_len, added_kv_proj_dim) before being projected + # to (batch_size, seq_len, cross_attention_dim). The norm is applied + # before the projection, so we need to use `added_kv_proj_dim` as + # the number of channels for the group norm. + norm_cross_num_channels = added_kv_proj_dim + else: + norm_cross_num_channels = self.cross_attention_dim + + self.norm_cross = nn.GroupNorm( + num_channels=norm_cross_num_channels, num_groups=cross_attention_norm_num_groups, eps=1e-5, affine=True + ) + else: + raise ValueError( + f"unknown cross_attention_norm: {cross_attention_norm}. Should be None, 'layer_norm' or 'group_norm'" + ) + + if USE_PEFT_BACKEND: + linear_cls = nn.Linear + else: + linear_cls = LoRACompatibleLinear + + self.linear_cls = linear_cls + self.to_q = linear_cls(query_dim, self.inner_dim, bias=bias) + + if not self.only_cross_attention: + # only relevant for the `AddedKVProcessor` classes + self.to_k = linear_cls(self.cross_attention_dim, self.inner_dim, bias=bias) + self.to_v = linear_cls(self.cross_attention_dim, self.inner_dim, bias=bias) + else: + self.to_k = None + self.to_v = None + + if self.added_kv_proj_dim is not None: + self.add_k_proj = linear_cls(added_kv_proj_dim, self.inner_dim) + self.add_v_proj = linear_cls(added_kv_proj_dim, self.inner_dim) + + self.to_out = nn.ModuleList([]) + self.to_out.append(linear_cls(self.inner_dim, self.out_dim, bias=out_bias)) + self.to_out.append(nn.Dropout(dropout)) + + # set attention processor + # We use the AttnProcessor2_0 by default when torch 2.x is used which uses + # torch.nn.functional.scaled_dot_product_attention for native Flash/memory_efficient_attention + # but only if it has the default `scale` argument. TODO remove scale_qk check when we move to torch 2.1 + if processor is None: + processor = ( + AttnProcessor2_0() if hasattr(F, "scaled_dot_product_attention") and self.scale_qk else AttnProcessor() + ) + self.set_processor(processor) + + def set_use_memory_efficient_attention_xformers( + self, use_memory_efficient_attention_xformers: bool, attention_op: Optional[Callable] = None + ) -> None: + r""" + Set whether to use memory efficient attention from `xformers` or not. + + Args: + use_memory_efficient_attention_xformers (`bool`): + Whether to use memory efficient attention from `xformers` or not. + attention_op (`Callable`, *optional*): + The attention operation to use. Defaults to `None` which uses the default attention operation from + `xformers`. + """ + is_lora = hasattr(self, "processor") and isinstance( + self.processor, + LORA_ATTENTION_PROCESSORS, + ) + is_custom_diffusion = hasattr(self, "processor") and isinstance( + self.processor, + (CustomDiffusionAttnProcessor, CustomDiffusionXFormersAttnProcessor, CustomDiffusionAttnProcessor2_0), + ) + is_added_kv_processor = hasattr(self, "processor") and isinstance( + self.processor, + ( + AttnAddedKVProcessor, + AttnAddedKVProcessor2_0, + SlicedAttnAddedKVProcessor, + XFormersAttnAddedKVProcessor, + LoRAAttnAddedKVProcessor, + ), + ) + + if use_memory_efficient_attention_xformers: + if is_added_kv_processor and (is_lora or is_custom_diffusion): + raise NotImplementedError( + f"Memory efficient attention is currently not supported for LoRA or custom diffusion for attention processor type {self.processor}" + ) + if not is_xformers_available(): + raise ModuleNotFoundError( + ( + "Refer to https://github.com/facebookresearch/xformers for more information on how to install" + " xformers" + ), + name="xformers", + ) + elif not torch.cuda.is_available(): + raise ValueError( + "torch.cuda.is_available() should be True but is False. xformers' memory efficient attention is" + " only available for GPU " + ) + else: + try: + # Make sure we can run the memory efficient attention + _ = xformers.ops.memory_efficient_attention( + torch.randn((1, 2, 40), device="cuda"), + torch.randn((1, 2, 40), device="cuda"), + torch.randn((1, 2, 40), device="cuda"), + ) + except Exception as e: + raise e + + if is_lora: + # TODO (sayakpaul): should we throw a warning if someone wants to use the xformers + # variant when using PT 2.0 now that we have LoRAAttnProcessor2_0? + processor = LoRAXFormersAttnProcessor( + hidden_size=self.processor.hidden_size, + cross_attention_dim=self.processor.cross_attention_dim, + rank=self.processor.rank, + attention_op=attention_op, + ) + processor.load_state_dict(self.processor.state_dict()) + processor.to(self.processor.to_q_lora.up.weight.device) + elif is_custom_diffusion: + processor = CustomDiffusionXFormersAttnProcessor( + train_kv=self.processor.train_kv, + train_q_out=self.processor.train_q_out, + hidden_size=self.processor.hidden_size, + cross_attention_dim=self.processor.cross_attention_dim, + attention_op=attention_op, + ) + processor.load_state_dict(self.processor.state_dict()) + if hasattr(self.processor, "to_k_custom_diffusion"): + processor.to(self.processor.to_k_custom_diffusion.weight.device) + elif is_added_kv_processor: + # TODO(Patrick, Suraj, William) - currently xformers doesn't work for UnCLIP + # which uses this type of cross attention ONLY because the attention mask of format + # [0, ..., -10.000, ..., 0, ...,] is not supported + # throw warning + logger.info( + "Memory efficient attention with `xformers` might currently not work correctly if an attention mask is required for the attention operation." + ) + processor = XFormersAttnAddedKVProcessor(attention_op=attention_op) + else: + processor = XFormersAttnProcessor(attention_op=attention_op) + else: + if is_lora: + attn_processor_class = ( + LoRAAttnProcessor2_0 if hasattr(F, "scaled_dot_product_attention") else LoRAAttnProcessor + ) + processor = attn_processor_class( + hidden_size=self.processor.hidden_size, + cross_attention_dim=self.processor.cross_attention_dim, + rank=self.processor.rank, + ) + processor.load_state_dict(self.processor.state_dict()) + processor.to(self.processor.to_q_lora.up.weight.device) + elif is_custom_diffusion: + attn_processor_class = ( + CustomDiffusionAttnProcessor2_0 + if hasattr(F, "scaled_dot_product_attention") + else CustomDiffusionAttnProcessor + ) + processor = attn_processor_class( + train_kv=self.processor.train_kv, + train_q_out=self.processor.train_q_out, + hidden_size=self.processor.hidden_size, + cross_attention_dim=self.processor.cross_attention_dim, + ) + processor.load_state_dict(self.processor.state_dict()) + if hasattr(self.processor, "to_k_custom_diffusion"): + processor.to(self.processor.to_k_custom_diffusion.weight.device) + else: + # set attention processor + # We use the AttnProcessor2_0 by default when torch 2.x is used which uses + # torch.nn.functional.scaled_dot_product_attention for native Flash/memory_efficient_attention + # but only if it has the default `scale` argument. TODO remove scale_qk check when we move to torch 2.1 + processor = ( + AttnProcessor2_0() + if hasattr(F, "scaled_dot_product_attention") and self.scale_qk + else AttnProcessor() + ) + + self.set_processor(processor) + + def set_attention_slice(self, slice_size: int) -> None: + r""" + Set the slice size for attention computation. + + Args: + slice_size (`int`): + The slice size for attention computation. + """ + if slice_size is not None and slice_size > self.sliceable_head_dim: + raise ValueError(f"slice_size {slice_size} has to be smaller or equal to {self.sliceable_head_dim}.") + + if slice_size is not None and self.added_kv_proj_dim is not None: + processor = SlicedAttnAddedKVProcessor(slice_size) + elif slice_size is not None: + processor = SlicedAttnProcessor(slice_size) + elif self.added_kv_proj_dim is not None: + processor = AttnAddedKVProcessor() + else: + # set attention processor + # We use the AttnProcessor2_0 by default when torch 2.x is used which uses + # torch.nn.functional.scaled_dot_product_attention for native Flash/memory_efficient_attention + # but only if it has the default `scale` argument. TODO remove scale_qk check when we move to torch 2.1 + processor = ( + AttnProcessor2_0() if hasattr(F, "scaled_dot_product_attention") and self.scale_qk else AttnProcessor() + ) + + self.set_processor(processor) + + def set_processor(self, processor: "AttnProcessor", _remove_lora: bool = False) -> None: + r""" + Set the attention processor to use. + + Args: + processor (`AttnProcessor`): + The attention processor to use. + _remove_lora (`bool`, *optional*, defaults to `False`): + Set to `True` to remove LoRA layers from the model. + """ + if not USE_PEFT_BACKEND and hasattr(self, "processor") and _remove_lora and self.to_q.lora_layer is not None: + deprecate( + "set_processor to offload LoRA", + "0.26.0", + "In detail, removing LoRA layers via calling `set_default_attn_processor` is deprecated. Please make sure to call `pipe.unload_lora_weights()` instead.", + ) + # TODO(Patrick, Sayak) - this can be deprecated once PEFT LoRA integration is complete + # We need to remove all LoRA layers + # Don't forget to remove ALL `_remove_lora` from the codebase + for module in self.modules(): + if hasattr(module, "set_lora_layer"): + module.set_lora_layer(None) + + # if current processor is in `self._modules` and if passed `processor` is not, we need to + # pop `processor` from `self._modules` + if ( + hasattr(self, "processor") + and isinstance(self.processor, torch.nn.Module) + and not isinstance(processor, torch.nn.Module) + ): + logger.info(f"You are removing possibly trained weights of {self.processor} with {processor}") + self._modules.pop("processor") + + self.processor = processor + + def get_processor(self, return_deprecated_lora: bool = False) -> "AttentionProcessor": + r""" + Get the attention processor in use. + + Args: + return_deprecated_lora (`bool`, *optional*, defaults to `False`): + Set to `True` to return the deprecated LoRA attention processor. + + Returns: + "AttentionProcessor": The attention processor in use. + """ + if not return_deprecated_lora: + return self.processor + + # TODO(Sayak, Patrick). The rest of the function is needed to ensure backwards compatible + # serialization format for LoRA Attention Processors. It should be deleted once the integration + # with PEFT is completed. + is_lora_activated = { + name: module.lora_layer is not None + for name, module in self.named_modules() + if hasattr(module, "lora_layer") + } + + # 1. if no layer has a LoRA activated we can return the processor as usual + if not any(is_lora_activated.values()): + return self.processor + + # If doesn't apply LoRA do `add_k_proj` or `add_v_proj` + is_lora_activated.pop("add_k_proj", None) + is_lora_activated.pop("add_v_proj", None) + # 2. else it is not posssible that only some layers have LoRA activated + if not all(is_lora_activated.values()): + raise ValueError( + f"Make sure that either all layers or no layers have LoRA activated, but have {is_lora_activated}" + ) + + # 3. And we need to merge the current LoRA layers into the corresponding LoRA attention processor + non_lora_processor_cls_name = self.processor.__class__.__name__ + lora_processor_cls = getattr(import_module(__name__), "LoRA" + non_lora_processor_cls_name) + + hidden_size = self.inner_dim + + # now create a LoRA attention processor from the LoRA layers + if lora_processor_cls in [LoRAAttnProcessor, LoRAAttnProcessor2_0, LoRAXFormersAttnProcessor]: + kwargs = { + "cross_attention_dim": self.cross_attention_dim, + "rank": self.to_q.lora_layer.rank, + "network_alpha": self.to_q.lora_layer.network_alpha, + "q_rank": self.to_q.lora_layer.rank, + "q_hidden_size": self.to_q.lora_layer.out_features, + "k_rank": self.to_k.lora_layer.rank, + "k_hidden_size": self.to_k.lora_layer.out_features, + "v_rank": self.to_v.lora_layer.rank, + "v_hidden_size": self.to_v.lora_layer.out_features, + "out_rank": self.to_out[0].lora_layer.rank, + "out_hidden_size": self.to_out[0].lora_layer.out_features, + } + + if hasattr(self.processor, "attention_op"): + kwargs["attention_op"] = self.processor.attention_op + + lora_processor = lora_processor_cls(hidden_size, **kwargs) + lora_processor.to_q_lora.load_state_dict(self.to_q.lora_layer.state_dict()) + lora_processor.to_k_lora.load_state_dict(self.to_k.lora_layer.state_dict()) + lora_processor.to_v_lora.load_state_dict(self.to_v.lora_layer.state_dict()) + lora_processor.to_out_lora.load_state_dict(self.to_out[0].lora_layer.state_dict()) + elif lora_processor_cls == LoRAAttnAddedKVProcessor: + lora_processor = lora_processor_cls( + hidden_size, + cross_attention_dim=self.add_k_proj.weight.shape[0], + rank=self.to_q.lora_layer.rank, + network_alpha=self.to_q.lora_layer.network_alpha, + ) + lora_processor.to_q_lora.load_state_dict(self.to_q.lora_layer.state_dict()) + lora_processor.to_k_lora.load_state_dict(self.to_k.lora_layer.state_dict()) + lora_processor.to_v_lora.load_state_dict(self.to_v.lora_layer.state_dict()) + lora_processor.to_out_lora.load_state_dict(self.to_out[0].lora_layer.state_dict()) + + # only save if used + if self.add_k_proj.lora_layer is not None: + lora_processor.add_k_proj_lora.load_state_dict(self.add_k_proj.lora_layer.state_dict()) + lora_processor.add_v_proj_lora.load_state_dict(self.add_v_proj.lora_layer.state_dict()) + else: + lora_processor.add_k_proj_lora = None + lora_processor.add_v_proj_lora = None + else: + raise ValueError(f"{lora_processor_cls} does not exist.") + + return lora_processor + + def forward( + self, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + **cross_attention_kwargs, + ) -> torch.Tensor: + r""" + The forward method of the `Attention` class. + + Args: + hidden_states (`torch.Tensor`): + The hidden states of the query. + encoder_hidden_states (`torch.Tensor`, *optional*): + The hidden states of the encoder. + attention_mask (`torch.Tensor`, *optional*): + The attention mask to use. If `None`, no mask is applied. + **cross_attention_kwargs: + Additional keyword arguments to pass along to the cross attention. + + Returns: + `torch.Tensor`: The output of the attention layer. + """ + # The `Attention` class can call different attention processors / attention functions + # here we simply pass along all tensors to the selected processor class + # For standard processors that are defined here, `**cross_attention_kwargs` is empty + return self.processor( + self, + hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=attention_mask, + **cross_attention_kwargs, + ) + + def batch_to_head_dim(self, tensor: torch.Tensor) -> torch.Tensor: + r""" + Reshape the tensor from `[batch_size, seq_len, dim]` to `[batch_size // heads, seq_len, dim * heads]`. `heads` + is the number of heads initialized while constructing the `Attention` class. + + Args: + tensor (`torch.Tensor`): The tensor to reshape. + + Returns: + `torch.Tensor`: The reshaped tensor. + """ + head_size = self.heads + batch_size, seq_len, dim = tensor.shape + tensor = tensor.reshape(batch_size // head_size, head_size, seq_len, dim) + tensor = tensor.permute(0, 2, 1, 3).reshape(batch_size // head_size, seq_len, dim * head_size) + return tensor + + def head_to_batch_dim(self, tensor: torch.Tensor, out_dim: int = 3) -> torch.Tensor: + r""" + Reshape the tensor from `[batch_size, seq_len, dim]` to `[batch_size, seq_len, heads, dim // heads]` `heads` is + the number of heads initialized while constructing the `Attention` class. + + Args: + tensor (`torch.Tensor`): The tensor to reshape. + out_dim (`int`, *optional*, defaults to `3`): The output dimension of the tensor. If `3`, the tensor is + reshaped to `[batch_size * heads, seq_len, dim // heads]`. + + Returns: + `torch.Tensor`: The reshaped tensor. + """ + head_size = self.heads + batch_size, seq_len, dim = tensor.shape + tensor = tensor.reshape(batch_size, seq_len, head_size, dim // head_size) + tensor = tensor.permute(0, 2, 1, 3) + + if out_dim == 3: + tensor = tensor.reshape(batch_size * head_size, seq_len, dim // head_size) + + return tensor + + def get_attention_scores( + self, query: torch.Tensor, key: torch.Tensor, attention_mask: torch.Tensor = None + ) -> torch.Tensor: + r""" + Compute the attention scores. + + Args: + query (`torch.Tensor`): The query tensor. + key (`torch.Tensor`): The key tensor. + attention_mask (`torch.Tensor`, *optional*): The attention mask to use. If `None`, no mask is applied. + + Returns: + `torch.Tensor`: The attention probabilities/scores. + """ + dtype = query.dtype + if self.upcast_attention: + query = query.float() + key = key.float() + + if attention_mask is None: + baddbmm_input = torch.empty( + query.shape[0], query.shape[1], key.shape[1], dtype=query.dtype, device=query.device + ) + beta = 0 + else: + baddbmm_input = attention_mask + beta = 1 + + attention_scores = torch.baddbmm( + baddbmm_input, + query, + key.transpose(-1, -2), + beta=beta, + alpha=self.scale, + ) + del baddbmm_input + + if self.upcast_softmax: + attention_scores = attention_scores.float() + + attention_probs = attention_scores.softmax(dim=-1) + del attention_scores + + attention_probs = attention_probs.to(dtype) + + return attention_probs + + def prepare_attention_mask( + self, attention_mask: torch.Tensor, target_length: int, batch_size: int, out_dim: int = 3 + ) -> torch.Tensor: + r""" + Prepare the attention mask for the attention computation. + + Args: + attention_mask (`torch.Tensor`): + The attention mask to prepare. + target_length (`int`): + The target length of the attention mask. This is the length of the attention mask after padding. + batch_size (`int`): + The batch size, which is used to repeat the attention mask. + out_dim (`int`, *optional*, defaults to `3`): + The output dimension of the attention mask. Can be either `3` or `4`. + + Returns: + `torch.Tensor`: The prepared attention mask. + """ + head_size = self.heads + if attention_mask is None: + return attention_mask + + current_length: int = attention_mask.shape[-1] + if current_length != target_length: + if attention_mask.device.type == "mps": + # HACK: MPS: Does not support padding by greater than dimension of input tensor. + # Instead, we can manually construct the padding tensor. + padding_shape = (attention_mask.shape[0], attention_mask.shape[1], target_length) + padding = torch.zeros(padding_shape, dtype=attention_mask.dtype, device=attention_mask.device) + attention_mask = torch.cat([attention_mask, padding], dim=2) + else: + # TODO: for pipelines such as stable-diffusion, padding cross-attn mask: + # we want to instead pad by (0, remaining_length), where remaining_length is: + # remaining_length: int = target_length - current_length + # TODO: re-enable tests/models/test_models_unet_2d_condition.py#test_model_xattn_padding + attention_mask = F.pad(attention_mask, (0, target_length), value=0.0) + + if out_dim == 3: + if attention_mask.shape[0] < batch_size * head_size: + attention_mask = attention_mask.repeat_interleave(head_size, dim=0) + elif out_dim == 4: + attention_mask = attention_mask.unsqueeze(1) + attention_mask = attention_mask.repeat_interleave(head_size, dim=1) + + return attention_mask + + def norm_encoder_hidden_states(self, encoder_hidden_states: torch.Tensor) -> torch.Tensor: + r""" + Normalize the encoder hidden states. Requires `self.norm_cross` to be specified when constructing the + `Attention` class. + + Args: + encoder_hidden_states (`torch.Tensor`): Hidden states of the encoder. + + Returns: + `torch.Tensor`: The normalized encoder hidden states. + """ + assert self.norm_cross is not None, "self.norm_cross must be defined to call self.norm_encoder_hidden_states" + + if isinstance(self.norm_cross, nn.LayerNorm): + encoder_hidden_states = self.norm_cross(encoder_hidden_states) + elif isinstance(self.norm_cross, nn.GroupNorm): + # Group norm norms along the channels dimension and expects + # input to be in the shape of (N, C, *). In this case, we want + # to norm along the hidden dimension, so we need to move + # (batch_size, sequence_length, hidden_size) -> + # (batch_size, hidden_size, sequence_length) + encoder_hidden_states = encoder_hidden_states.transpose(1, 2) + encoder_hidden_states = self.norm_cross(encoder_hidden_states) + encoder_hidden_states = encoder_hidden_states.transpose(1, 2) + else: + assert False + + return encoder_hidden_states + + @torch.no_grad() + def fuse_projections(self, fuse=True): + is_cross_attention = self.cross_attention_dim != self.query_dim + device = self.to_q.weight.data.device + dtype = self.to_q.weight.data.dtype + + if not is_cross_attention: + # fetch weight matrices. + concatenated_weights = torch.cat([self.to_q.weight.data, self.to_k.weight.data, self.to_v.weight.data]) + in_features = concatenated_weights.shape[1] + out_features = concatenated_weights.shape[0] + + # create a new single projection layer and copy over the weights. + self.to_qkv = self.linear_cls(in_features, out_features, bias=False, device=device, dtype=dtype) + self.to_qkv.weight.copy_(concatenated_weights) + + else: + concatenated_weights = torch.cat([self.to_k.weight.data, self.to_v.weight.data]) + in_features = concatenated_weights.shape[1] + out_features = concatenated_weights.shape[0] + + self.to_kv = self.linear_cls(in_features, out_features, bias=False, device=device, dtype=dtype) + self.to_kv.weight.copy_(concatenated_weights) + + self.fused_projections = fuse + + +class AttnProcessor: + r""" + Default processor for performing attention-related computations. + """ + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + temb: Optional[torch.FloatTensor] = None, + scale: float = 1.0, + ) -> torch.Tensor: + residual = hidden_states + + args = () if USE_PEFT_BACKEND else (scale,) + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states, *args) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states, *args) + value = attn.to_v(encoder_hidden_states, *args) + + query = attn.head_to_batch_dim(query) + key = attn.head_to_batch_dim(key) + value = attn.head_to_batch_dim(value) + + attention_probs = attn.get_attention_scores(query, key, attention_mask) + hidden_states = torch.bmm(attention_probs, value) + hidden_states = attn.batch_to_head_dim(hidden_states) + + # linear proj + hidden_states = attn.to_out[0](hidden_states, *args) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + + +class CustomDiffusionAttnProcessor(nn.Module): + r""" + Processor for implementing attention for the Custom Diffusion method. + + Args: + train_kv (`bool`, defaults to `True`): + Whether to newly train the key and value matrices corresponding to the text features. + train_q_out (`bool`, defaults to `True`): + Whether to newly train query matrices corresponding to the latent image features. + hidden_size (`int`, *optional*, defaults to `None`): + The hidden size of the attention layer. + cross_attention_dim (`int`, *optional*, defaults to `None`): + The number of channels in the `encoder_hidden_states`. + out_bias (`bool`, defaults to `True`): + Whether to include the bias parameter in `train_q_out`. + dropout (`float`, *optional*, defaults to 0.0): + The dropout probability to use. + """ + + def __init__( + self, + train_kv: bool = True, + train_q_out: bool = True, + hidden_size: Optional[int] = None, + cross_attention_dim: Optional[int] = None, + out_bias: bool = True, + dropout: float = 0.0, + ): + super().__init__() + self.train_kv = train_kv + self.train_q_out = train_q_out + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + + # `_custom_diffusion` id for easy serialization and loading. + if self.train_kv: + self.to_k_custom_diffusion = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + self.to_v_custom_diffusion = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + if self.train_q_out: + self.to_q_custom_diffusion = nn.Linear(hidden_size, hidden_size, bias=False) + self.to_out_custom_diffusion = nn.ModuleList([]) + self.to_out_custom_diffusion.append(nn.Linear(hidden_size, hidden_size, bias=out_bias)) + self.to_out_custom_diffusion.append(nn.Dropout(dropout)) + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.Tensor: + batch_size, sequence_length, _ = hidden_states.shape + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + if self.train_q_out: + query = self.to_q_custom_diffusion(hidden_states).to(attn.to_q.weight.dtype) + else: + query = attn.to_q(hidden_states.to(attn.to_q.weight.dtype)) + + if encoder_hidden_states is None: + crossattn = False + encoder_hidden_states = hidden_states + else: + crossattn = True + if attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + if self.train_kv: + key = self.to_k_custom_diffusion(encoder_hidden_states.to(self.to_k_custom_diffusion.weight.dtype)) + value = self.to_v_custom_diffusion(encoder_hidden_states.to(self.to_v_custom_diffusion.weight.dtype)) + key = key.to(attn.to_q.weight.dtype) + value = value.to(attn.to_q.weight.dtype) + else: + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + if crossattn: + detach = torch.ones_like(key) + detach[:, :1, :] = detach[:, :1, :] * 0.0 + key = detach * key + (1 - detach) * key.detach() + value = detach * value + (1 - detach) * value.detach() + + query = attn.head_to_batch_dim(query) + key = attn.head_to_batch_dim(key) + value = attn.head_to_batch_dim(value) + + attention_probs = attn.get_attention_scores(query, key, attention_mask) + hidden_states = torch.bmm(attention_probs, value) + hidden_states = attn.batch_to_head_dim(hidden_states) + + if self.train_q_out: + # linear proj + hidden_states = self.to_out_custom_diffusion[0](hidden_states) + # dropout + hidden_states = self.to_out_custom_diffusion[1](hidden_states) + else: + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + return hidden_states + + +class AttnAddedKVProcessor: + r""" + Processor for performing attention-related computations with extra learnable key and value matrices for the text + encoder. + """ + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + scale: float = 1.0, + ) -> torch.Tensor: + residual = hidden_states + + args = () if USE_PEFT_BACKEND else (scale,) + + hidden_states = hidden_states.view(hidden_states.shape[0], hidden_states.shape[1], -1).transpose(1, 2) + batch_size, sequence_length, _ = hidden_states.shape + + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states, *args) + query = attn.head_to_batch_dim(query) + + encoder_hidden_states_key_proj = attn.add_k_proj(encoder_hidden_states, *args) + encoder_hidden_states_value_proj = attn.add_v_proj(encoder_hidden_states, *args) + encoder_hidden_states_key_proj = attn.head_to_batch_dim(encoder_hidden_states_key_proj) + encoder_hidden_states_value_proj = attn.head_to_batch_dim(encoder_hidden_states_value_proj) + + if not attn.only_cross_attention: + key = attn.to_k(hidden_states, *args) + value = attn.to_v(hidden_states, *args) + key = attn.head_to_batch_dim(key) + value = attn.head_to_batch_dim(value) + key = torch.cat([encoder_hidden_states_key_proj, key], dim=1) + value = torch.cat([encoder_hidden_states_value_proj, value], dim=1) + else: + key = encoder_hidden_states_key_proj + value = encoder_hidden_states_value_proj + + attention_probs = attn.get_attention_scores(query, key, attention_mask) + hidden_states = torch.bmm(attention_probs, value) + hidden_states = attn.batch_to_head_dim(hidden_states) + + # linear proj + hidden_states = attn.to_out[0](hidden_states, *args) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + hidden_states = hidden_states.transpose(-1, -2).reshape(residual.shape) + hidden_states = hidden_states + residual + + return hidden_states + + +class AttnAddedKVProcessor2_0: + r""" + Processor for performing scaled dot-product attention (enabled by default if you're using PyTorch 2.0), with extra + learnable key and value matrices for the text encoder. + """ + + def __init__(self): + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError( + "AttnAddedKVProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0." + ) + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + scale: float = 1.0, + ) -> torch.Tensor: + residual = hidden_states + + args = () if USE_PEFT_BACKEND else (scale,) + + hidden_states = hidden_states.view(hidden_states.shape[0], hidden_states.shape[1], -1).transpose(1, 2) + batch_size, sequence_length, _ = hidden_states.shape + + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size, out_dim=4) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states, *args) + query = attn.head_to_batch_dim(query, out_dim=4) + + encoder_hidden_states_key_proj = attn.add_k_proj(encoder_hidden_states) + encoder_hidden_states_value_proj = attn.add_v_proj(encoder_hidden_states) + encoder_hidden_states_key_proj = attn.head_to_batch_dim(encoder_hidden_states_key_proj, out_dim=4) + encoder_hidden_states_value_proj = attn.head_to_batch_dim(encoder_hidden_states_value_proj, out_dim=4) + + if not attn.only_cross_attention: + key = attn.to_k(hidden_states, *args) + value = attn.to_v(hidden_states, *args) + key = attn.head_to_batch_dim(key, out_dim=4) + value = attn.head_to_batch_dim(value, out_dim=4) + key = torch.cat([encoder_hidden_states_key_proj, key], dim=2) + value = torch.cat([encoder_hidden_states_value_proj, value], dim=2) + else: + key = encoder_hidden_states_key_proj + value = encoder_hidden_states_value_proj + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False + ) + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, residual.shape[1]) + + # linear proj + hidden_states = attn.to_out[0](hidden_states, *args) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + hidden_states = hidden_states.transpose(-1, -2).reshape(residual.shape) + hidden_states = hidden_states + residual + + return hidden_states + + +class XFormersAttnAddedKVProcessor: + r""" + Processor for implementing memory efficient attention using xFormers. + + Args: + attention_op (`Callable`, *optional*, defaults to `None`): + The base + [operator](https://facebookresearch.github.io/xformers/components/ops.html#xformers.ops.AttentionOpBase) to + use as the attention operator. It is recommended to set to `None`, and allow xFormers to choose the best + operator. + """ + + def __init__(self, attention_op: Optional[Callable] = None): + self.attention_op = attention_op + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.Tensor: + residual = hidden_states + hidden_states = hidden_states.view(hidden_states.shape[0], hidden_states.shape[1], -1).transpose(1, 2) + batch_size, sequence_length, _ = hidden_states.shape + + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + query = attn.head_to_batch_dim(query) + + encoder_hidden_states_key_proj = attn.add_k_proj(encoder_hidden_states) + encoder_hidden_states_value_proj = attn.add_v_proj(encoder_hidden_states) + encoder_hidden_states_key_proj = attn.head_to_batch_dim(encoder_hidden_states_key_proj) + encoder_hidden_states_value_proj = attn.head_to_batch_dim(encoder_hidden_states_value_proj) + + if not attn.only_cross_attention: + key = attn.to_k(hidden_states) + value = attn.to_v(hidden_states) + key = attn.head_to_batch_dim(key) + value = attn.head_to_batch_dim(value) + key = torch.cat([encoder_hidden_states_key_proj, key], dim=1) + value = torch.cat([encoder_hidden_states_value_proj, value], dim=1) + else: + key = encoder_hidden_states_key_proj + value = encoder_hidden_states_value_proj + + hidden_states = xformers.ops.memory_efficient_attention( + query, key, value, attn_bias=attention_mask, op=self.attention_op, scale=attn.scale + ) + hidden_states = hidden_states.to(query.dtype) + hidden_states = attn.batch_to_head_dim(hidden_states) + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + hidden_states = hidden_states.transpose(-1, -2).reshape(residual.shape) + hidden_states = hidden_states + residual + + return hidden_states + + +class XFormersAttnProcessor: + r""" + Processor for implementing memory efficient attention using xFormers. + + Args: + attention_op (`Callable`, *optional*, defaults to `None`): + The base + [operator](https://facebookresearch.github.io/xformers/components/ops.html#xformers.ops.AttentionOpBase) to + use as the attention operator. It is recommended to set to `None`, and allow xFormers to choose the best + operator. + """ + + def __init__(self, attention_op: Optional[Callable] = None): + self.attention_op = attention_op + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + temb: Optional[torch.FloatTensor] = None, + scale: float = 1.0, + ) -> torch.FloatTensor: + residual = hidden_states + + args = () if USE_PEFT_BACKEND else (scale,) + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, key_tokens, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + + attention_mask = attn.prepare_attention_mask(attention_mask, key_tokens, batch_size) + if attention_mask is not None: + # expand our mask's singleton query_tokens dimension: + # [batch*heads, 1, key_tokens] -> + # [batch*heads, query_tokens, key_tokens] + # so that it can be added as a bias onto the attention scores that xformers computes: + # [batch*heads, query_tokens, key_tokens] + # we do this explicitly because xformers doesn't broadcast the singleton dimension for us. + _, query_tokens, _ = hidden_states.shape + attention_mask = attention_mask.expand(-1, query_tokens, -1) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states, *args) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states, *args) + value = attn.to_v(encoder_hidden_states, *args) + + query = attn.head_to_batch_dim(query).contiguous() + key = attn.head_to_batch_dim(key).contiguous() + value = attn.head_to_batch_dim(value).contiguous() + + hidden_states = xformers.ops.memory_efficient_attention( + query, key, value, attn_bias=attention_mask, op=self.attention_op, scale=attn.scale + ) + hidden_states = hidden_states.to(query.dtype) + hidden_states = attn.batch_to_head_dim(hidden_states) + + # linear proj + hidden_states = attn.to_out[0](hidden_states, *args) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + + +class AttnProcessor2_0: + r""" + Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0). + """ + + def __init__(self): + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.") + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + temb: Optional[torch.FloatTensor] = None, + scale: float = 1.0, + **kwargs, + ) -> torch.FloatTensor: + residual = hidden_states + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + + if attention_mask is not None: + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + # scaled_dot_product_attention expects attention_mask shape to be + # (batch, heads, source_length, target_length) + attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1]) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + args = () if USE_PEFT_BACKEND else (scale,) + query = attn.to_q(hidden_states, *args) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states, *args) + value = attn.to_v(encoder_hidden_states, *args) + + inner_dim = key.shape[-1] + head_dim = inner_dim // attn.heads + + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + hidden_states = hidden_states.to(query.dtype) + + # linear proj + hidden_states = attn.to_out[0](hidden_states, *args) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + + +class FusedAttnProcessor2_0: + r""" + Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0). + It uses fused projection layers. For self-attention modules, all projection matrices (i.e., query, + key, value) are fused. For cross-attention modules, key and value projection matrices are fused. + + + + This API is currently 🧪 experimental in nature and can change in future. + + + """ + + def __init__(self): + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError( + "FusedAttnProcessor2_0 requires at least PyTorch 2.0, to use it. Please upgrade PyTorch to > 2.0." + ) + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + temb: Optional[torch.FloatTensor] = None, + scale: float = 1.0, + ) -> torch.FloatTensor: + residual = hidden_states + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + + if attention_mask is not None: + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + # scaled_dot_product_attention expects attention_mask shape to be + # (batch, heads, source_length, target_length) + attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1]) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + args = () if USE_PEFT_BACKEND else (scale,) + if encoder_hidden_states is None: + qkv = attn.to_qkv(hidden_states, *args) + split_size = qkv.shape[-1] // 3 + query, key, value = torch.split(qkv, split_size, dim=-1) + else: + if attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + query = attn.to_q(hidden_states, *args) + + kv = attn.to_kv(encoder_hidden_states, *args) + split_size = kv.shape[-1] // 2 + key, value = torch.split(kv, split_size, dim=-1) + + inner_dim = key.shape[-1] + head_dim = inner_dim // attn.heads + + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + hidden_states = hidden_states.to(query.dtype) + + # linear proj + hidden_states = attn.to_out[0](hidden_states, *args) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + + +class CustomDiffusionXFormersAttnProcessor(nn.Module): + r""" + Processor for implementing memory efficient attention using xFormers for the Custom Diffusion method. + + Args: + train_kv (`bool`, defaults to `True`): + Whether to newly train the key and value matrices corresponding to the text features. + train_q_out (`bool`, defaults to `True`): + Whether to newly train query matrices corresponding to the latent image features. + hidden_size (`int`, *optional*, defaults to `None`): + The hidden size of the attention layer. + cross_attention_dim (`int`, *optional*, defaults to `None`): + The number of channels in the `encoder_hidden_states`. + out_bias (`bool`, defaults to `True`): + Whether to include the bias parameter in `train_q_out`. + dropout (`float`, *optional*, defaults to 0.0): + The dropout probability to use. + attention_op (`Callable`, *optional*, defaults to `None`): + The base + [operator](https://facebookresearch.github.io/xformers/components/ops.html#xformers.ops.AttentionOpBase) to use + as the attention operator. It is recommended to set to `None`, and allow xFormers to choose the best operator. + """ + + def __init__( + self, + train_kv: bool = True, + train_q_out: bool = False, + hidden_size: Optional[int] = None, + cross_attention_dim: Optional[int] = None, + out_bias: bool = True, + dropout: float = 0.0, + attention_op: Optional[Callable] = None, + ): + super().__init__() + self.train_kv = train_kv + self.train_q_out = train_q_out + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + self.attention_op = attention_op + + # `_custom_diffusion` id for easy serialization and loading. + if self.train_kv: + self.to_k_custom_diffusion = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + self.to_v_custom_diffusion = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + if self.train_q_out: + self.to_q_custom_diffusion = nn.Linear(hidden_size, hidden_size, bias=False) + self.to_out_custom_diffusion = nn.ModuleList([]) + self.to_out_custom_diffusion.append(nn.Linear(hidden_size, hidden_size, bias=out_bias)) + self.to_out_custom_diffusion.append(nn.Dropout(dropout)) + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + + if self.train_q_out: + query = self.to_q_custom_diffusion(hidden_states).to(attn.to_q.weight.dtype) + else: + query = attn.to_q(hidden_states.to(attn.to_q.weight.dtype)) + + if encoder_hidden_states is None: + crossattn = False + encoder_hidden_states = hidden_states + else: + crossattn = True + if attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + if self.train_kv: + key = self.to_k_custom_diffusion(encoder_hidden_states.to(self.to_k_custom_diffusion.weight.dtype)) + value = self.to_v_custom_diffusion(encoder_hidden_states.to(self.to_v_custom_diffusion.weight.dtype)) + key = key.to(attn.to_q.weight.dtype) + value = value.to(attn.to_q.weight.dtype) + else: + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + if crossattn: + detach = torch.ones_like(key) + detach[:, :1, :] = detach[:, :1, :] * 0.0 + key = detach * key + (1 - detach) * key.detach() + value = detach * value + (1 - detach) * value.detach() + + query = attn.head_to_batch_dim(query).contiguous() + key = attn.head_to_batch_dim(key).contiguous() + value = attn.head_to_batch_dim(value).contiguous() + + hidden_states = xformers.ops.memory_efficient_attention( + query, key, value, attn_bias=attention_mask, op=self.attention_op, scale=attn.scale + ) + hidden_states = hidden_states.to(query.dtype) + hidden_states = attn.batch_to_head_dim(hidden_states) + + if self.train_q_out: + # linear proj + hidden_states = self.to_out_custom_diffusion[0](hidden_states) + # dropout + hidden_states = self.to_out_custom_diffusion[1](hidden_states) + else: + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + return hidden_states + + +class CustomDiffusionAttnProcessor2_0(nn.Module): + r""" + Processor for implementing attention for the Custom Diffusion method using PyTorch 2.0’s memory-efficient scaled + dot-product attention. + + Args: + train_kv (`bool`, defaults to `True`): + Whether to newly train the key and value matrices corresponding to the text features. + train_q_out (`bool`, defaults to `True`): + Whether to newly train query matrices corresponding to the latent image features. + hidden_size (`int`, *optional*, defaults to `None`): + The hidden size of the attention layer. + cross_attention_dim (`int`, *optional*, defaults to `None`): + The number of channels in the `encoder_hidden_states`. + out_bias (`bool`, defaults to `True`): + Whether to include the bias parameter in `train_q_out`. + dropout (`float`, *optional*, defaults to 0.0): + The dropout probability to use. + """ + + def __init__( + self, + train_kv: bool = True, + train_q_out: bool = True, + hidden_size: Optional[int] = None, + cross_attention_dim: Optional[int] = None, + out_bias: bool = True, + dropout: float = 0.0, + ): + super().__init__() + self.train_kv = train_kv + self.train_q_out = train_q_out + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + + # `_custom_diffusion` id for easy serialization and loading. + if self.train_kv: + self.to_k_custom_diffusion = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + self.to_v_custom_diffusion = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + if self.train_q_out: + self.to_q_custom_diffusion = nn.Linear(hidden_size, hidden_size, bias=False) + self.to_out_custom_diffusion = nn.ModuleList([]) + self.to_out_custom_diffusion.append(nn.Linear(hidden_size, hidden_size, bias=out_bias)) + self.to_out_custom_diffusion.append(nn.Dropout(dropout)) + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + batch_size, sequence_length, _ = hidden_states.shape + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + if self.train_q_out: + query = self.to_q_custom_diffusion(hidden_states) + else: + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + crossattn = False + encoder_hidden_states = hidden_states + else: + crossattn = True + if attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + if self.train_kv: + key = self.to_k_custom_diffusion(encoder_hidden_states.to(self.to_k_custom_diffusion.weight.dtype)) + value = self.to_v_custom_diffusion(encoder_hidden_states.to(self.to_v_custom_diffusion.weight.dtype)) + key = key.to(attn.to_q.weight.dtype) + value = value.to(attn.to_q.weight.dtype) + + else: + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + if crossattn: + detach = torch.ones_like(key) + detach[:, :1, :] = detach[:, :1, :] * 0.0 + key = detach * key + (1 - detach) * key.detach() + value = detach * value + (1 - detach) * value.detach() + + inner_dim = hidden_states.shape[-1] + + head_dim = inner_dim // attn.heads + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + hidden_states = hidden_states.to(query.dtype) + + if self.train_q_out: + # linear proj + hidden_states = self.to_out_custom_diffusion[0](hidden_states) + # dropout + hidden_states = self.to_out_custom_diffusion[1](hidden_states) + else: + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + return hidden_states + + +class SlicedAttnProcessor: + r""" + Processor for implementing sliced attention. + + Args: + slice_size (`int`, *optional*): + The number of steps to compute attention. Uses as many slices as `attention_head_dim // slice_size`, and + `attention_head_dim` must be a multiple of the `slice_size`. + """ + + def __init__(self, slice_size: int): + self.slice_size = slice_size + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + residual = hidden_states + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + dim = query.shape[-1] + query = attn.head_to_batch_dim(query) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + key = attn.head_to_batch_dim(key) + value = attn.head_to_batch_dim(value) + + batch_size_attention, query_tokens, _ = query.shape + hidden_states = torch.zeros( + (batch_size_attention, query_tokens, dim // attn.heads), device=query.device, dtype=query.dtype + ) + + for i in range(batch_size_attention // self.slice_size): + start_idx = i * self.slice_size + end_idx = (i + 1) * self.slice_size + + query_slice = query[start_idx:end_idx] + key_slice = key[start_idx:end_idx] + attn_mask_slice = attention_mask[start_idx:end_idx] if attention_mask is not None else None + + attn_slice = attn.get_attention_scores(query_slice, key_slice, attn_mask_slice) + + attn_slice = torch.bmm(attn_slice, value[start_idx:end_idx]) + + hidden_states[start_idx:end_idx] = attn_slice + + hidden_states = attn.batch_to_head_dim(hidden_states) + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + + +class SlicedAttnAddedKVProcessor: + r""" + Processor for implementing sliced attention with extra learnable key and value matrices for the text encoder. + + Args: + slice_size (`int`, *optional*): + The number of steps to compute attention. Uses as many slices as `attention_head_dim // slice_size`, and + `attention_head_dim` must be a multiple of the `slice_size`. + """ + + def __init__(self, slice_size): + self.slice_size = slice_size + + def __call__( + self, + attn: "Attention", + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + temb: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + residual = hidden_states + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + hidden_states = hidden_states.view(hidden_states.shape[0], hidden_states.shape[1], -1).transpose(1, 2) + + batch_size, sequence_length, _ = hidden_states.shape + + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + dim = query.shape[-1] + query = attn.head_to_batch_dim(query) + + encoder_hidden_states_key_proj = attn.add_k_proj(encoder_hidden_states) + encoder_hidden_states_value_proj = attn.add_v_proj(encoder_hidden_states) + + encoder_hidden_states_key_proj = attn.head_to_batch_dim(encoder_hidden_states_key_proj) + encoder_hidden_states_value_proj = attn.head_to_batch_dim(encoder_hidden_states_value_proj) + + if not attn.only_cross_attention: + key = attn.to_k(hidden_states) + value = attn.to_v(hidden_states) + key = attn.head_to_batch_dim(key) + value = attn.head_to_batch_dim(value) + key = torch.cat([encoder_hidden_states_key_proj, key], dim=1) + value = torch.cat([encoder_hidden_states_value_proj, value], dim=1) + else: + key = encoder_hidden_states_key_proj + value = encoder_hidden_states_value_proj + + batch_size_attention, query_tokens, _ = query.shape + hidden_states = torch.zeros( + (batch_size_attention, query_tokens, dim // attn.heads), device=query.device, dtype=query.dtype + ) + + for i in range(batch_size_attention // self.slice_size): + start_idx = i * self.slice_size + end_idx = (i + 1) * self.slice_size + + query_slice = query[start_idx:end_idx] + key_slice = key[start_idx:end_idx] + attn_mask_slice = attention_mask[start_idx:end_idx] if attention_mask is not None else None + + attn_slice = attn.get_attention_scores(query_slice, key_slice, attn_mask_slice) + + attn_slice = torch.bmm(attn_slice, value[start_idx:end_idx]) + + hidden_states[start_idx:end_idx] = attn_slice + + hidden_states = attn.batch_to_head_dim(hidden_states) + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + hidden_states = hidden_states.transpose(-1, -2).reshape(residual.shape) + hidden_states = hidden_states + residual + + return hidden_states + + +class SpatialNorm(nn.Module): + """ + Spatially conditioned normalization as defined in https://arxiv.org/abs/2209.09002. + + Args: + f_channels (`int`): + The number of channels for input to group normalization layer, and output of the spatial norm layer. + zq_channels (`int`): + The number of channels for the quantized vector as described in the paper. + """ + + def __init__( + self, + f_channels: int, + zq_channels: int, + ): + super().__init__() + self.norm_layer = nn.GroupNorm(num_channels=f_channels, num_groups=32, eps=1e-6, affine=True) + self.conv_y = nn.Conv2d(zq_channels, f_channels, kernel_size=1, stride=1, padding=0) + self.conv_b = nn.Conv2d(zq_channels, f_channels, kernel_size=1, stride=1, padding=0) + + def forward(self, f: torch.FloatTensor, zq: torch.FloatTensor) -> torch.FloatTensor: + f_size = f.shape[-2:] + zq = F.interpolate(zq, size=f_size, mode="nearest") + norm_f = self.norm_layer(f) + new_f = norm_f * self.conv_y(zq) + self.conv_b(zq) + return new_f + + +## Deprecated +class LoRAAttnProcessor(nn.Module): + r""" + Processor for implementing the LoRA attention mechanism. + + Args: + hidden_size (`int`, *optional*): + The hidden size of the attention layer. + cross_attention_dim (`int`, *optional*): + The number of channels in the `encoder_hidden_states`. + rank (`int`, defaults to 4): + The dimension of the LoRA update matrices. + network_alpha (`int`, *optional*): + Equivalent to `alpha` but it's usage is specific to Kohya (A1111) style LoRAs. + kwargs (`dict`): + Additional keyword arguments to pass to the `LoRALinearLayer` layers. + """ + + def __init__( + self, + hidden_size: int, + cross_attention_dim: Optional[int] = None, + rank: int = 4, + network_alpha: Optional[int] = None, + **kwargs, + ): + super().__init__() + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + self.rank = rank + + q_rank = kwargs.pop("q_rank", None) + q_hidden_size = kwargs.pop("q_hidden_size", None) + q_rank = q_rank if q_rank is not None else rank + q_hidden_size = q_hidden_size if q_hidden_size is not None else hidden_size + + v_rank = kwargs.pop("v_rank", None) + v_hidden_size = kwargs.pop("v_hidden_size", None) + v_rank = v_rank if v_rank is not None else rank + v_hidden_size = v_hidden_size if v_hidden_size is not None else hidden_size + + out_rank = kwargs.pop("out_rank", None) + out_hidden_size = kwargs.pop("out_hidden_size", None) + out_rank = out_rank if out_rank is not None else rank + out_hidden_size = out_hidden_size if out_hidden_size is not None else hidden_size + + self.to_q_lora = LoRALinearLayer(q_hidden_size, q_hidden_size, q_rank, network_alpha) + self.to_k_lora = LoRALinearLayer(cross_attention_dim or hidden_size, hidden_size, rank, network_alpha) + self.to_v_lora = LoRALinearLayer(cross_attention_dim or v_hidden_size, v_hidden_size, v_rank, network_alpha) + self.to_out_lora = LoRALinearLayer(out_hidden_size, out_hidden_size, out_rank, network_alpha) + + def __call__(self, attn: Attention, hidden_states: torch.FloatTensor, *args, **kwargs) -> torch.FloatTensor: + self_cls_name = self.__class__.__name__ + deprecate( + self_cls_name, + "0.26.0", + ( + f"Make sure use {self_cls_name[4:]} instead by setting" + "LoRA layers to `self.{to_q,to_k,to_v,to_out[0]}.lora_layer` respectively. This will be done automatically when using" + " `LoraLoaderMixin.load_lora_weights`" + ), + ) + attn.to_q.lora_layer = self.to_q_lora.to(hidden_states.device) + attn.to_k.lora_layer = self.to_k_lora.to(hidden_states.device) + attn.to_v.lora_layer = self.to_v_lora.to(hidden_states.device) + attn.to_out[0].lora_layer = self.to_out_lora.to(hidden_states.device) + + attn._modules.pop("processor") + attn.processor = AttnProcessor() + return attn.processor(attn, hidden_states, *args, **kwargs) + + +class LoRAAttnProcessor2_0(nn.Module): + r""" + Processor for implementing the LoRA attention mechanism using PyTorch 2.0's memory-efficient scaled dot-product + attention. + + Args: + hidden_size (`int`): + The hidden size of the attention layer. + cross_attention_dim (`int`, *optional*): + The number of channels in the `encoder_hidden_states`. + rank (`int`, defaults to 4): + The dimension of the LoRA update matrices. + network_alpha (`int`, *optional*): + Equivalent to `alpha` but it's usage is specific to Kohya (A1111) style LoRAs. + kwargs (`dict`): + Additional keyword arguments to pass to the `LoRALinearLayer` layers. + """ + + def __init__( + self, + hidden_size: int, + cross_attention_dim: Optional[int] = None, + rank: int = 4, + network_alpha: Optional[int] = None, + **kwargs, + ): + super().__init__() + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.") + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + self.rank = rank + + q_rank = kwargs.pop("q_rank", None) + q_hidden_size = kwargs.pop("q_hidden_size", None) + q_rank = q_rank if q_rank is not None else rank + q_hidden_size = q_hidden_size if q_hidden_size is not None else hidden_size + + v_rank = kwargs.pop("v_rank", None) + v_hidden_size = kwargs.pop("v_hidden_size", None) + v_rank = v_rank if v_rank is not None else rank + v_hidden_size = v_hidden_size if v_hidden_size is not None else hidden_size + + out_rank = kwargs.pop("out_rank", None) + out_hidden_size = kwargs.pop("out_hidden_size", None) + out_rank = out_rank if out_rank is not None else rank + out_hidden_size = out_hidden_size if out_hidden_size is not None else hidden_size + + self.to_q_lora = LoRALinearLayer(q_hidden_size, q_hidden_size, q_rank, network_alpha) + self.to_k_lora = LoRALinearLayer(cross_attention_dim or hidden_size, hidden_size, rank, network_alpha) + self.to_v_lora = LoRALinearLayer(cross_attention_dim or v_hidden_size, v_hidden_size, v_rank, network_alpha) + self.to_out_lora = LoRALinearLayer(out_hidden_size, out_hidden_size, out_rank, network_alpha) + + def __call__(self, attn: Attention, hidden_states: torch.FloatTensor, *args, **kwargs) -> torch.FloatTensor: + self_cls_name = self.__class__.__name__ + deprecate( + self_cls_name, + "0.26.0", + ( + f"Make sure use {self_cls_name[4:]} instead by setting" + "LoRA layers to `self.{to_q,to_k,to_v,to_out[0]}.lora_layer` respectively. This will be done automatically when using" + " `LoraLoaderMixin.load_lora_weights`" + ), + ) + attn.to_q.lora_layer = self.to_q_lora.to(hidden_states.device) + attn.to_k.lora_layer = self.to_k_lora.to(hidden_states.device) + attn.to_v.lora_layer = self.to_v_lora.to(hidden_states.device) + attn.to_out[0].lora_layer = self.to_out_lora.to(hidden_states.device) + + attn._modules.pop("processor") + attn.processor = AttnProcessor2_0() + return attn.processor(attn, hidden_states, *args, **kwargs) + + +class LoRAXFormersAttnProcessor(nn.Module): + r""" + Processor for implementing the LoRA attention mechanism with memory efficient attention using xFormers. + + Args: + hidden_size (`int`, *optional*): + The hidden size of the attention layer. + cross_attention_dim (`int`, *optional*): + The number of channels in the `encoder_hidden_states`. + rank (`int`, defaults to 4): + The dimension of the LoRA update matrices. + attention_op (`Callable`, *optional*, defaults to `None`): + The base + [operator](https://facebookresearch.github.io/xformers/components/ops.html#xformers.ops.AttentionOpBase) to + use as the attention operator. It is recommended to set to `None`, and allow xFormers to choose the best + operator. + network_alpha (`int`, *optional*): + Equivalent to `alpha` but it's usage is specific to Kohya (A1111) style LoRAs. + kwargs (`dict`): + Additional keyword arguments to pass to the `LoRALinearLayer` layers. + """ + + def __init__( + self, + hidden_size: int, + cross_attention_dim: int, + rank: int = 4, + attention_op: Optional[Callable] = None, + network_alpha: Optional[int] = None, + **kwargs, + ): + super().__init__() + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + self.rank = rank + self.attention_op = attention_op + + q_rank = kwargs.pop("q_rank", None) + q_hidden_size = kwargs.pop("q_hidden_size", None) + q_rank = q_rank if q_rank is not None else rank + q_hidden_size = q_hidden_size if q_hidden_size is not None else hidden_size + + v_rank = kwargs.pop("v_rank", None) + v_hidden_size = kwargs.pop("v_hidden_size", None) + v_rank = v_rank if v_rank is not None else rank + v_hidden_size = v_hidden_size if v_hidden_size is not None else hidden_size + + out_rank = kwargs.pop("out_rank", None) + out_hidden_size = kwargs.pop("out_hidden_size", None) + out_rank = out_rank if out_rank is not None else rank + out_hidden_size = out_hidden_size if out_hidden_size is not None else hidden_size + + self.to_q_lora = LoRALinearLayer(q_hidden_size, q_hidden_size, q_rank, network_alpha) + self.to_k_lora = LoRALinearLayer(cross_attention_dim or hidden_size, hidden_size, rank, network_alpha) + self.to_v_lora = LoRALinearLayer(cross_attention_dim or v_hidden_size, v_hidden_size, v_rank, network_alpha) + self.to_out_lora = LoRALinearLayer(out_hidden_size, out_hidden_size, out_rank, network_alpha) + + def __call__(self, attn: Attention, hidden_states: torch.FloatTensor, *args, **kwargs) -> torch.FloatTensor: + self_cls_name = self.__class__.__name__ + deprecate( + self_cls_name, + "0.26.0", + ( + f"Make sure use {self_cls_name[4:]} instead by setting" + "LoRA layers to `self.{to_q,to_k,to_v,add_k_proj,add_v_proj,to_out[0]}.lora_layer` respectively. This will be done automatically when using" + " `LoraLoaderMixin.load_lora_weights`" + ), + ) + attn.to_q.lora_layer = self.to_q_lora.to(hidden_states.device) + attn.to_k.lora_layer = self.to_k_lora.to(hidden_states.device) + attn.to_v.lora_layer = self.to_v_lora.to(hidden_states.device) + attn.to_out[0].lora_layer = self.to_out_lora.to(hidden_states.device) + + attn._modules.pop("processor") + attn.processor = XFormersAttnProcessor() + return attn.processor(attn, hidden_states, *args, **kwargs) + + +class LoRAAttnAddedKVProcessor(nn.Module): + r""" + Processor for implementing the LoRA attention mechanism with extra learnable key and value matrices for the text + encoder. + + Args: + hidden_size (`int`, *optional*): + The hidden size of the attention layer. + cross_attention_dim (`int`, *optional*, defaults to `None`): + The number of channels in the `encoder_hidden_states`. + rank (`int`, defaults to 4): + The dimension of the LoRA update matrices. + network_alpha (`int`, *optional*): + Equivalent to `alpha` but it's usage is specific to Kohya (A1111) style LoRAs. + kwargs (`dict`): + Additional keyword arguments to pass to the `LoRALinearLayer` layers. + """ + + def __init__( + self, + hidden_size: int, + cross_attention_dim: Optional[int] = None, + rank: int = 4, + network_alpha: Optional[int] = None, + ): + super().__init__() + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + self.rank = rank + + self.to_q_lora = LoRALinearLayer(hidden_size, hidden_size, rank, network_alpha) + self.add_k_proj_lora = LoRALinearLayer(cross_attention_dim or hidden_size, hidden_size, rank, network_alpha) + self.add_v_proj_lora = LoRALinearLayer(cross_attention_dim or hidden_size, hidden_size, rank, network_alpha) + self.to_k_lora = LoRALinearLayer(hidden_size, hidden_size, rank, network_alpha) + self.to_v_lora = LoRALinearLayer(hidden_size, hidden_size, rank, network_alpha) + self.to_out_lora = LoRALinearLayer(hidden_size, hidden_size, rank, network_alpha) + + def __call__(self, attn: Attention, hidden_states: torch.FloatTensor, *args, **kwargs) -> torch.FloatTensor: + self_cls_name = self.__class__.__name__ + deprecate( + self_cls_name, + "0.26.0", + ( + f"Make sure use {self_cls_name[4:]} instead by setting" + "LoRA layers to `self.{to_q,to_k,to_v,add_k_proj,add_v_proj,to_out[0]}.lora_layer` respectively. This will be done automatically when using" + " `LoraLoaderMixin.load_lora_weights`" + ), + ) + attn.to_q.lora_layer = self.to_q_lora.to(hidden_states.device) + attn.to_k.lora_layer = self.to_k_lora.to(hidden_states.device) + attn.to_v.lora_layer = self.to_v_lora.to(hidden_states.device) + attn.to_out[0].lora_layer = self.to_out_lora.to(hidden_states.device) + + attn._modules.pop("processor") + attn.processor = AttnAddedKVProcessor() + return attn.processor(attn, hidden_states, *args, **kwargs) + + +class IPAdapterAttnProcessor(nn.Module): + r""" + Attention processor for IP-Adapater. + + Args: + hidden_size (`int`): + The hidden size of the attention layer. + cross_attention_dim (`int`): + The number of channels in the `encoder_hidden_states`. + num_tokens (`int`, defaults to 4): + The context length of the image features. + scale (`float`, defaults to 1.0): + the weight scale of image prompt. + """ + + def __init__(self, hidden_size, cross_attention_dim=None, num_tokens=4, scale=1.0): + super().__init__() + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + self.num_tokens = num_tokens + self.scale = scale + + self.to_k_ip = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + self.to_v_ip = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + + def __call__( + self, + attn, + hidden_states, + encoder_hidden_states=None, + attention_mask=None, + temb=None, + scale=1.0, + ): + if scale != 1.0: + logger.warning("`scale` of IPAttnProcessor should be set with `set_ip_adapter_scale`.") + residual = hidden_states + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + # split hidden states + end_pos = encoder_hidden_states.shape[1] - self.num_tokens + encoder_hidden_states, ip_hidden_states = ( + encoder_hidden_states[:, :end_pos, :], + encoder_hidden_states[:, end_pos:, :], + ) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + query = attn.head_to_batch_dim(query) + key = attn.head_to_batch_dim(key) + value = attn.head_to_batch_dim(value) + + attention_probs = attn.get_attention_scores(query, key, attention_mask) + hidden_states = torch.bmm(attention_probs, value) + hidden_states = attn.batch_to_head_dim(hidden_states) + + # for ip-adapter + ip_key = self.to_k_ip(ip_hidden_states) + ip_value = self.to_v_ip(ip_hidden_states) + + ip_key = attn.head_to_batch_dim(ip_key) + ip_value = attn.head_to_batch_dim(ip_value) + + ip_attention_probs = attn.get_attention_scores(query, ip_key, None) + ip_hidden_states = torch.bmm(ip_attention_probs, ip_value) + ip_hidden_states = attn.batch_to_head_dim(ip_hidden_states) + + hidden_states = hidden_states + self.scale * ip_hidden_states + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + +class VPTemporalAdapterAttnProcessor2_0(torch.nn.Module): + r""" + Attention processor for IP-Adapter for PyTorch 2.0. + + Args: + hidden_size (`int`): + The hidden size of the attention layer. + cross_attention_dim (`int`): + The number of channels in the `encoder_hidden_states`. + num_tokens (`int`, `Tuple[int]` or `List[int]`, defaults to `(4,)`): + The context length of the image features. + scale (`float` or `List[float]`, defaults to 1.0): + the weight scale of image prompt. + """ + + """ + Support frame-wise VP-Adapter + encoder_hidden_states : I(num of ip_adapters), B, N * T(num of time condition), C + ip_adapter_masks(bool): (I, B, N * T, C) == encoder_hidden_states.shape + + """ + + def __init__(self, hidden_size, cross_attention_dim=None, num_tokens=(4,), scale=1.0): + super().__init__() + + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError( + f"{self.__class__.__name__} requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0." + ) + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + + if not isinstance(num_tokens, (tuple, list)): + num_tokens = [num_tokens] + self.num_tokens = num_tokens + + if not isinstance(scale, list): + scale = [scale] * len(num_tokens) + if len(scale) != len(num_tokens): + raise ValueError("`scale` should be a list of integers with the same length as `num_tokens`.") + self.scale = scale + + self.to_k_ip = nn.ModuleList( + [nn.Linear(cross_attention_dim, hidden_size, bias=False) for _ in range(len(num_tokens))] + ) + self.to_v_ip = nn.ModuleList( + [nn.Linear(cross_attention_dim, hidden_size, bias=False) for _ in range(len(num_tokens))] + ) + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + temb: Optional[torch.FloatTensor] = None, + scale: float = 1.0, + ip_adapter_masks: Optional[torch.FloatTensor] = None, + time_conditions: Optional[list] = None, + audio_length_in_s: Optional[int] = None, + ): + residual = hidden_states + + # separate ip_hidden_states from encoder_hidden_states + if encoder_hidden_states is not None: + if isinstance(encoder_hidden_states, tuple): + encoder_hidden_states, ip_hidden_states = encoder_hidden_states + else: + deprecation_message = ( + "You have passed a tensor as `encoder_hidden_states`. This is deprecated and will be removed in a future release." + " Please make sure to update your script to pass `encoder_hidden_states` as a tuple to suppress this warning." + ) + deprecate("encoder_hidden_states not a tuple", "1.0.0", deprecation_message, standard_warn=False) + end_pos = encoder_hidden_states.shape[1] - self.num_tokens[0] + encoder_hidden_states, ip_hidden_states = ( + encoder_hidden_states[:, :end_pos, :], + [encoder_hidden_states[:, end_pos:, :]], + ) + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + + if attention_mask is not None: + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + # scaled_dot_product_attention expects attention_mask shape to be + # (batch, heads, source_length, target_length) + attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1]) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + inner_dim = key.shape[-1] + head_dim = inner_dim // attn.heads + + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + hidden_states = hidden_states.to(query.dtype) + + if ip_adapter_masks is not None: + if not isinstance(ip_adapter_masks, List): + # for backward compatibility, we accept `ip_adapter_mask` as a tensor of shape [num_ip_adapter, 1, height, width] + ip_adapter_masks = list(ip_adapter_masks.unsqueeze(1)) + if not (len(ip_adapter_masks) == len(self.scale) == len(ip_hidden_states)): + raise ValueError( + f"Length of ip_adapter_masks array ({len(ip_adapter_masks)}) must match " + f"length of self.scale array ({len(self.scale)}) and number of ip_hidden_states " + f"({len(ip_hidden_states)})" + ) + else: + for index, (mask, scale, ip_state) in enumerate(zip(ip_adapter_masks, self.scale, ip_hidden_states)): + if not isinstance(mask, torch.Tensor) or mask.ndim != 4: + raise ValueError( + "Each element of the ip_adapter_masks array should be a tensor with shape " + "[1, num_images_for_ip_adapter, height, width]." + " Please use `IPAdapterMaskProcessor` to preprocess your mask" + ) + if mask.shape[1] != ip_state.shape[1]: + raise ValueError( + f"Number of masks ({mask.shape[1]}) does not match " + f"number of ip images ({ip_state.shape[1]}) at index {index}" + ) + if isinstance(scale, list) and not len(scale) == mask.shape[1]: + raise ValueError( + f"Number of masks ({mask.shape[1]}) does not match " + f"number of scales ({len(scale)}) at index {index}" + ) + else: + ip_adapter_masks = [None] * len(self.scale) + # for ip-adapter + for current_ip_hidden_states, scale, to_k_ip, to_v_ip, mask in zip( + ip_hidden_states, self.scale, self.to_k_ip, self.to_v_ip, ip_adapter_masks + ): + skip = False + if isinstance(scale, list): + if all(s == 0 for s in scale): + skip = True + elif scale == 0: + skip = True + if not skip: + time_condition_masks = None + for time_condition in time_conditions: + # hard code + time_condition_mask = torch.zeros(( + batch_size, + int(math.sqrt(hidden_states.shape[1]) // 2), + int(2 * math.sqrt(hidden_states.shape[1])), + )).bool().to(device=hidden_states.device) + mel_latent_length = time_condition_mask.shape[-1] + time_start, time_end = \ + int(time_condition[0] // audio_length_in_s * mel_latent_length),\ + int(time_condition[1] // audio_length_in_s * mel_latent_length) + + time_condition_mask[:, :, time_start:time_end] = True + time_condition_mask = time_condition_mask.flatten(-2).unsqueeze(-1).repeat(1, 1, 4) + if time_condition_masks is None: + time_condition_masks = time_condition_mask + else: + time_condition_masks = torch.cat([time_condition_masks, time_condition_mask], dim=-1) + + current_ip_hidden_states = rearrange(current_ip_hidden_states, 'L B N C -> B (L N) C') + ip_key = to_k_ip(current_ip_hidden_states) + ip_value = to_v_ip(current_ip_hidden_states) + + ip_key = ip_key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + ip_value = ip_value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + time_condition_masks = time_condition_masks.unsqueeze(1).repeat(1, attn.heads, 1, 1) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + current_ip_hidden_states = F.scaled_dot_product_attention( + query, ip_key, ip_value, attn_mask=time_condition_masks, dropout_p=0.0, is_causal=False + ) + + current_ip_hidden_states = current_ip_hidden_states.transpose(1, 2).reshape( + batch_size, -1, attn.heads * head_dim + ) + current_ip_hidden_states = current_ip_hidden_states.to(query.dtype) + + hidden_states = hidden_states + scale * current_ip_hidden_states + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + +class IPAdapterAttnProcessor2_0(torch.nn.Module): + r""" + Attention processor for IP-Adapter for PyTorch 2.0. + + Args: + hidden_size (`int`): + The hidden size of the attention layer. + cross_attention_dim (`int`): + The number of channels in the `encoder_hidden_states`. + num_tokens (`int`, `Tuple[int]` or `List[int]`, defaults to `(4,)`): + The context length of the image features. + scale (`float` or `List[float]`, defaults to 1.0): + the weight scale of image prompt. + """ + + def __init__(self, hidden_size, cross_attention_dim=None, num_tokens=(4,), scale=1.0): + super().__init__() + + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError( + f"{self.__class__.__name__} requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0." + ) + + self.hidden_size = hidden_size + self.cross_attention_dim = cross_attention_dim + + if not isinstance(num_tokens, (tuple, list)): + num_tokens = [num_tokens] + self.num_tokens = num_tokens + + if not isinstance(scale, list): + scale = [scale] * len(num_tokens) + if len(scale) != len(num_tokens): + raise ValueError("`scale` should be a list of integers with the same length as `num_tokens`.") + self.scale = scale + self.to_k_ip = nn.ModuleList( + [nn.Linear(cross_attention_dim, hidden_size, bias=False) for _ in range(len(num_tokens))] + ) + self.to_v_ip = nn.ModuleList( + [nn.Linear(cross_attention_dim, hidden_size, bias=False) for _ in range(len(num_tokens))] + ) + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + temb: Optional[torch.FloatTensor] = None, + scale: float = 1.0, + ip_adapter_masks: Optional[torch.FloatTensor] = None, + ): + residual = hidden_states + + # separate ip_hidden_states from encoder_hidden_states + if encoder_hidden_states is not None: + if isinstance(encoder_hidden_states, tuple): + encoder_hidden_states, ip_hidden_states = encoder_hidden_states + else: + deprecation_message = ( + "You have passed a tensor as `encoder_hidden_states`. This is deprecated and will be removed in a future release." + " Please make sure to update your script to pass `encoder_hidden_states` as a tuple to suppress this warning." + ) + deprecate("encoder_hidden_states not a tuple", "1.0.0", deprecation_message, standard_warn=False) + end_pos = encoder_hidden_states.shape[1] - self.num_tokens[0] + encoder_hidden_states, ip_hidden_states = ( + encoder_hidden_states[:, :end_pos, :], + [encoder_hidden_states[:, end_pos:, :]], + ) + + if attn.spatial_norm is not None: + hidden_states = attn.spatial_norm(hidden_states, temb) + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape + ) + + if attention_mask is not None: + attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) + # scaled_dot_product_attention expects attention_mask shape to be + # (batch, heads, source_length, target_length) + attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1]) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + inner_dim = key.shape[-1] + head_dim = inner_dim // attn.heads + + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + hidden_states = hidden_states.to(query.dtype) + + if ip_adapter_masks is not None: + if not isinstance(ip_adapter_masks, List): + # for backward compatibility, we accept `ip_adapter_mask` as a tensor of shape [num_ip_adapter, 1, height, width] + ip_adapter_masks = list(ip_adapter_masks.unsqueeze(1)) + if not (len(ip_adapter_masks) == len(self.scale) == len(ip_hidden_states)): + raise ValueError( + f"Length of ip_adapter_masks array ({len(ip_adapter_masks)}) must match " + f"length of self.scale array ({len(self.scale)}) and number of ip_hidden_states " + f"({len(ip_hidden_states)})" + ) + else: + for index, (mask, scale, ip_state) in enumerate(zip(ip_adapter_masks, self.scale, ip_hidden_states)): + if not isinstance(mask, torch.Tensor) or mask.ndim != 4: + raise ValueError( + "Each element of the ip_adapter_masks array should be a tensor with shape " + "[1, num_images_for_ip_adapter, height, width]." + " Please use `IPAdapterMaskProcessor` to preprocess your mask" + ) + if mask.shape[1] != ip_state.shape[1]: + raise ValueError( + f"Number of masks ({mask.shape[1]}) does not match " + f"number of ip images ({ip_state.shape[1]}) at index {index}" + ) + if isinstance(scale, list) and not len(scale) == mask.shape[1]: + raise ValueError( + f"Number of masks ({mask.shape[1]}) does not match " + f"number of scales ({len(scale)}) at index {index}" + ) + else: + ip_adapter_masks = [None] * len(self.scale) + + # for ip-adapter + for current_ip_hidden_states, scale, to_k_ip, to_v_ip, mask in zip( + ip_hidden_states, self.scale, self.to_k_ip, self.to_v_ip, ip_adapter_masks + ): + skip = False + if isinstance(scale, list): + if all(s == 0 for s in scale): + skip = True + elif scale == 0: + skip = True + if not skip: + ip_key = to_k_ip(current_ip_hidden_states) + ip_value = to_v_ip(current_ip_hidden_states) + + ip_key = ip_key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + ip_value = ip_value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + current_ip_hidden_states = F.scaled_dot_product_attention( + query, ip_key, ip_value, attn_mask=None, dropout_p=0.0, is_causal=False + ) + + current_ip_hidden_states = current_ip_hidden_states.transpose(1, 2).reshape( + batch_size, -1, attn.heads * head_dim + ) + current_ip_hidden_states = current_ip_hidden_states.to(query.dtype) + + hidden_states = hidden_states + scale * current_ip_hidden_states + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + +LORA_ATTENTION_PROCESSORS = ( + LoRAAttnProcessor, + LoRAAttnProcessor2_0, + LoRAXFormersAttnProcessor, + LoRAAttnAddedKVProcessor, +) + +ADDED_KV_ATTENTION_PROCESSORS = ( + AttnAddedKVProcessor, + SlicedAttnAddedKVProcessor, + AttnAddedKVProcessor2_0, + XFormersAttnAddedKVProcessor, + LoRAAttnAddedKVProcessor, +) + +CROSS_ATTENTION_PROCESSORS = ( + AttnProcessor, + AttnProcessor2_0, + XFormersAttnProcessor, + SlicedAttnProcessor, + LoRAAttnProcessor, + LoRAAttnProcessor2_0, + LoRAXFormersAttnProcessor, + IPAdapterAttnProcessor, + IPAdapterAttnProcessor2_0, +) + +AttentionProcessor = Union[ + AttnProcessor, + AttnProcessor2_0, + FusedAttnProcessor2_0, + XFormersAttnProcessor, + SlicedAttnProcessor, + AttnAddedKVProcessor, + SlicedAttnAddedKVProcessor, + AttnAddedKVProcessor2_0, + XFormersAttnAddedKVProcessor, + CustomDiffusionAttnProcessor, + CustomDiffusionXFormersAttnProcessor, + CustomDiffusionAttnProcessor2_0, + # deprecated + LoRAAttnProcessor, + LoRAAttnProcessor2_0, + LoRAXFormersAttnProcessor, + LoRAAttnAddedKVProcessor, +] \ No newline at end of file diff --git a/foleycrafter/models/auffusion/dual_transformer_2d.py b/foleycrafter/models/auffusion/dual_transformer_2d.py new file mode 100644 index 0000000000000000000000000000000000000000..c3f27b61e001347f0093c039ad10ae79975b7691 --- /dev/null +++ b/foleycrafter/models/auffusion/dual_transformer_2d.py @@ -0,0 +1,156 @@ +# Copyright 2023 The HuggingFace 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 +# limitations under the License. +from typing import Optional + +from torch import nn + +from foleycrafter.models.auffusion.transformer_2d \ + import Transformer2DModel, Transformer2DModelOutput + + +class DualTransformer2DModel(nn.Module): + """ + Dual transformer wrapper that combines two `Transformer2DModel`s for mixed inference. + + Parameters: + num_attention_heads (`int`, *optional*, defaults to 16): The number of heads to use for multi-head attention. + attention_head_dim (`int`, *optional*, defaults to 88): The number of channels in each head. + in_channels (`int`, *optional*): + Pass if the input is continuous. The number of channels in the input and output. + num_layers (`int`, *optional*, defaults to 1): The number of layers of Transformer blocks to use. + dropout (`float`, *optional*, defaults to 0.1): The dropout probability to use. + cross_attention_dim (`int`, *optional*): The number of encoder_hidden_states dimensions to use. + sample_size (`int`, *optional*): Pass if the input is discrete. The width of the latent images. + Note that this is fixed at training time as it is used for learning a number of position embeddings. See + `ImagePositionalEmbeddings`. + num_vector_embeds (`int`, *optional*): + Pass if the input is discrete. The number of classes of the vector embeddings of the latent pixels. + Includes the class for the masked latent pixel. + activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward. + num_embeds_ada_norm ( `int`, *optional*): Pass if at least one of the norm_layers is `AdaLayerNorm`. + The number of diffusion steps used during training. Note that this is fixed at training time as it is used + to learn a number of embeddings that are added to the hidden states. During inference, you can denoise for + up to but not more than steps than `num_embeds_ada_norm`. + attention_bias (`bool`, *optional*): + Configure if the TransformerBlocks' attention should contain a bias parameter. + """ + + def __init__( + self, + num_attention_heads: int = 16, + attention_head_dim: int = 88, + in_channels: Optional[int] = None, + num_layers: int = 1, + dropout: float = 0.0, + norm_num_groups: int = 32, + cross_attention_dim: Optional[int] = None, + attention_bias: bool = False, + sample_size: Optional[int] = None, + num_vector_embeds: Optional[int] = None, + activation_fn: str = "geglu", + num_embeds_ada_norm: Optional[int] = None, + ): + super().__init__() + self.transformers = nn.ModuleList( + [ + Transformer2DModel( + num_attention_heads=num_attention_heads, + attention_head_dim=attention_head_dim, + in_channels=in_channels, + num_layers=num_layers, + dropout=dropout, + norm_num_groups=norm_num_groups, + cross_attention_dim=cross_attention_dim, + attention_bias=attention_bias, + sample_size=sample_size, + num_vector_embeds=num_vector_embeds, + activation_fn=activation_fn, + num_embeds_ada_norm=num_embeds_ada_norm, + ) + for _ in range(2) + ] + ) + + # Variables that can be set by a pipeline: + + # The ratio of transformer1 to transformer2's output states to be combined during inference + self.mix_ratio = 0.5 + + # The shape of `encoder_hidden_states` is expected to be + # `(batch_size, condition_lengths[0]+condition_lengths[1], num_features)` + self.condition_lengths = [77, 257] + + # Which transformer to use to encode which condition. + # E.g. `(1, 0)` means that we'll use `transformers[1](conditions[0])` and `transformers[0](conditions[1])` + self.transformer_index_for_condition = [1, 0] + + def forward( + self, + hidden_states, + encoder_hidden_states, + timestep=None, + attention_mask=None, + cross_attention_kwargs=None, + return_dict: bool = True, + ): + """ + Args: + hidden_states ( When discrete, `torch.LongTensor` of shape `(batch size, num latent pixels)`. + When continuous, `torch.FloatTensor` of shape `(batch size, channel, height, width)`): Input + hidden_states. + encoder_hidden_states ( `torch.LongTensor` of shape `(batch size, encoder_hidden_states dim)`, *optional*): + Conditional embeddings for cross attention layer. If not given, cross-attention defaults to + self-attention. + timestep ( `torch.long`, *optional*): + Optional timestep to be applied as an embedding in AdaLayerNorm's. Used to indicate denoising step. + attention_mask (`torch.FloatTensor`, *optional*): + Optional attention mask to be applied in Attention. + cross_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under + `self.processor` in + [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`models.unet_2d_condition.UNet2DConditionOutput`] instead of a plain tuple. + + Returns: + [`~models.transformer_2d.Transformer2DModelOutput`] or `tuple`: + [`~models.transformer_2d.Transformer2DModelOutput`] if `return_dict` is True, otherwise a `tuple`. When + returning a tuple, the first element is the sample tensor. + """ + input_states = hidden_states + + encoded_states = [] + tokens_start = 0 + # attention_mask is not used yet + for i in range(2): + # for each of the two transformers, pass the corresponding condition tokens + condition_state = encoder_hidden_states[:, tokens_start : tokens_start + self.condition_lengths[i]] + transformer_index = self.transformer_index_for_condition[i] + encoded_state = self.transformers[transformer_index]( + input_states, + encoder_hidden_states=condition_state, + timestep=timestep, + cross_attention_kwargs=cross_attention_kwargs, + return_dict=False, + )[0] + encoded_states.append(encoded_state - input_states) + tokens_start += self.condition_lengths[i] + + output_states = encoded_states[0] * self.mix_ratio + encoded_states[1] * (1 - self.mix_ratio) + output_states = output_states + input_states + + if not return_dict: + return (output_states,) + + return Transformer2DModelOutput(sample=output_states) \ No newline at end of file diff --git a/foleycrafter/models/auffusion/loaders/ip_adapter.py b/foleycrafter/models/auffusion/loaders/ip_adapter.py new file mode 100644 index 0000000000000000000000000000000000000000..faba325670450f3a3d2885ce32e74e3811ba8405 --- /dev/null +++ b/foleycrafter/models/auffusion/loaders/ip_adapter.py @@ -0,0 +1,520 @@ +# Copyright 2024 The HuggingFace 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 +# limitations under the License. + +from pathlib import Path +from typing import Dict, List, Optional, Union + +import torch +from huggingface_hub.utils import validate_hf_hub_args +from safetensors import safe_open + +from diffusers.models.modeling_utils import _LOW_CPU_MEM_USAGE_DEFAULT +from diffusers.utils import ( + _get_model_file, + is_accelerate_available, + is_torch_version, + is_transformers_available, + logging, +) + + +if is_transformers_available(): + from transformers import ( + CLIPImageProcessor, + CLIPVisionModelWithProjection, + ) + + from diffusers.models.attention_processor import ( + IPAdapterAttnProcessor, + ) + +from foleycrafter.models.auffusion.attention_processor import IPAdapterAttnProcessor2_0, VPTemporalAdapterAttnProcessor2_0 + +logger = logging.get_logger(__name__) + + +class IPAdapterMixin: + """Mixin for handling IP Adapters.""" + + @validate_hf_hub_args + def load_ip_adapter( + self, + pretrained_model_name_or_path_or_dict: Union[str, List[str], Dict[str, torch.Tensor]], + subfolder: Union[str, List[str]], + weight_name: Union[str, List[str]], + image_encoder_folder: Optional[str] = "image_encoder", + **kwargs, + ): + """ + Parameters: + pretrained_model_name_or_path_or_dict (`str` or `List[str]` or `os.PathLike` or `List[os.PathLike]` or `dict` or `List[dict]`): + Can be either: + + - A string, the *model id* (for example `google/ddpm-celebahq-256`) of a pretrained model hosted on + the Hub. + - A path to a *directory* (for example `./my_model_directory`) containing the model weights saved + with [`ModelMixin.save_pretrained`]. + - A [torch state + dict](https://pytorch.org/tutorials/beginner/saving_loading_models.html#what-is-a-state-dict). + subfolder (`str` or `List[str]`): + The subfolder location of a model file within a larger model repository on the Hub or locally. + If a list is passed, it should have the same length as `weight_name`. + weight_name (`str` or `List[str]`): + The name of the weight file to load. If a list is passed, it should have the same length as + `weight_name`. + image_encoder_folder (`str`, *optional*, defaults to `image_encoder`): + The subfolder location of the image encoder within a larger model repository on the Hub or locally. + Pass `None` to not load the image encoder. If the image encoder is located in a folder inside `subfolder`, + you only need to pass the name of the folder that contains image encoder weights, e.g. `image_encoder_folder="image_encoder"`. + If the image encoder is located in a folder other than `subfolder`, you should pass the path to the folder that contains image encoder weights, + for example, `image_encoder_folder="different_subfolder/image_encoder"`. + cache_dir (`Union[str, os.PathLike]`, *optional*): + Path to a directory where a downloaded pretrained model configuration is cached if the standard cache + is not used. + force_download (`bool`, *optional*, defaults to `False`): + Whether or not to force the (re-)download of the model weights and configuration files, overriding the + cached versions if they exist. + resume_download (`bool`, *optional*, defaults to `False`): + Whether or not to resume downloading the model weights and configuration files. If set to `False`, any + incompletely downloaded files are deleted. + proxies (`Dict[str, str]`, *optional*): + A dictionary of proxy servers to use by protocol or endpoint, for example, `{'http': 'foo.bar:3128', + 'http://hostname': 'foo.bar:4012'}`. The proxies are used on each request. + local_files_only (`bool`, *optional*, defaults to `False`): + Whether to only load local model weights and configuration files or not. If set to `True`, the model + won't be downloaded from the Hub. + token (`str` or *bool*, *optional*): + The token to use as HTTP bearer authorization for remote files. If `True`, the token generated from + `diffusers-cli login` (stored in `~/.huggingface`) is used. + revision (`str`, *optional*, defaults to `"main"`): + The specific model version to use. It can be a branch name, a tag name, a commit id, or any identifier + allowed by Git. + low_cpu_mem_usage (`bool`, *optional*, defaults to `True` if torch version >= 1.9.0 else `False`): + Speed up model loading only loading the pretrained weights and not initializing the weights. This also + tries to not use more than 1x model size in CPU memory (including peak memory) while loading the model. + Only supported for PyTorch >= 1.9.0. If you are using an older version of PyTorch, setting this + argument to `True` will raise an error. + """ + + # handle the list inputs for multiple IP Adapters + if not isinstance(weight_name, list): + weight_name = [weight_name] + + if not isinstance(pretrained_model_name_or_path_or_dict, list): + pretrained_model_name_or_path_or_dict = [pretrained_model_name_or_path_or_dict] + if len(pretrained_model_name_or_path_or_dict) == 1: + pretrained_model_name_or_path_or_dict = pretrained_model_name_or_path_or_dict * len(weight_name) + + if not isinstance(subfolder, list): + subfolder = [subfolder] + if len(subfolder) == 1: + subfolder = subfolder * len(weight_name) + + if len(weight_name) != len(pretrained_model_name_or_path_or_dict): + raise ValueError("`weight_name` and `pretrained_model_name_or_path_or_dict` must have the same length.") + + if len(weight_name) != len(subfolder): + raise ValueError("`weight_name` and `subfolder` must have the same length.") + + # Load the main state dict first. + cache_dir = kwargs.pop("cache_dir", None) + force_download = kwargs.pop("force_download", False) + resume_download = kwargs.pop("resume_download", False) + proxies = kwargs.pop("proxies", None) + local_files_only = kwargs.pop("local_files_only", None) + token = kwargs.pop("token", None) + revision = kwargs.pop("revision", None) + low_cpu_mem_usage = kwargs.pop("low_cpu_mem_usage", _LOW_CPU_MEM_USAGE_DEFAULT) + + if low_cpu_mem_usage and not is_accelerate_available(): + low_cpu_mem_usage = False + logger.warning( + "Cannot initialize model with low cpu memory usage because `accelerate` was not found in the" + " environment. Defaulting to `low_cpu_mem_usage=False`. It is strongly recommended to install" + " `accelerate` for faster and less memory-intense model loading. You can do so with: \n```\npip" + " install accelerate\n```\n." + ) + + if low_cpu_mem_usage is True and not is_torch_version(">=", "1.9.0"): + raise NotImplementedError( + "Low memory initialization requires torch >= 1.9.0. Please either update your PyTorch version or set" + " `low_cpu_mem_usage=False`." + ) + + user_agent = { + "file_type": "attn_procs_weights", + "framework": "pytorch", + } + state_dicts = [] + for pretrained_model_name_or_path_or_dict, weight_name, subfolder in zip( + pretrained_model_name_or_path_or_dict, weight_name, subfolder + ): + if not isinstance(pretrained_model_name_or_path_or_dict, dict): + model_file = _get_model_file( + pretrained_model_name_or_path_or_dict, + weights_name=weight_name, + cache_dir=cache_dir, + force_download=force_download, + resume_download=resume_download, + proxies=proxies, + local_files_only=local_files_only, + token=token, + revision=revision, + subfolder=subfolder, + user_agent=user_agent, + ) + if weight_name.endswith(".safetensors"): + state_dict = {"image_proj": {}, "ip_adapter": {}} + with safe_open(model_file, framework="pt", device="cpu") as f: + for key in f.keys(): + if key.startswith("image_proj."): + state_dict["image_proj"][key.replace("image_proj.", "")] = f.get_tensor(key) + elif key.startswith("ip_adapter."): + state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = f.get_tensor(key) + else: + state_dict = torch.load(model_file, map_location="cpu") + else: + state_dict = pretrained_model_name_or_path_or_dict + + keys = list(state_dict.keys()) + if keys != ["image_proj", "ip_adapter"]: + raise ValueError("Required keys are (`image_proj` and `ip_adapter`) missing from the state dict.") + + state_dicts.append(state_dict) + + # load CLIP image encoder here if it has not been registered to the pipeline yet + if hasattr(self, "image_encoder") and getattr(self, "image_encoder", None) is None: + if image_encoder_folder is not None: + if not isinstance(pretrained_model_name_or_path_or_dict, dict): + logger.info(f"loading image_encoder from {pretrained_model_name_or_path_or_dict}") + if image_encoder_folder.count("/") == 0: + image_encoder_subfolder = Path(subfolder, image_encoder_folder).as_posix() + else: + image_encoder_subfolder = Path(image_encoder_folder).as_posix() + + image_encoder = CLIPVisionModelWithProjection.from_pretrained( + pretrained_model_name_or_path_or_dict, + subfolder=image_encoder_subfolder, + low_cpu_mem_usage=low_cpu_mem_usage, + ).to(self.device, dtype=self.dtype) + self.register_modules(image_encoder=image_encoder) + else: + raise ValueError( + "`image_encoder` cannot be loaded because `pretrained_model_name_or_path_or_dict` is a state dict." + ) + else: + logger.warning( + "image_encoder is not loaded since `image_encoder_folder=None` passed. You will not be able to use `ip_adapter_image` when calling the pipeline with IP-Adapter." + "Use `ip_adapter_image_embeds` to pass pre-generated image embedding instead." + ) + + # create feature extractor if it has not been registered to the pipeline yet + if hasattr(self, "feature_extractor") and getattr(self, "feature_extractor", None) is None: + feature_extractor = CLIPImageProcessor() + self.register_modules(feature_extractor=feature_extractor) + + # load ip-adapter into unet + unet = getattr(self, self.unet_name) if not hasattr(self, "unet") else self.unet + unet._load_ip_adapter_weights(state_dicts) + + def set_ip_adapter_scale(self, scale): + """ + Sets the conditioning scale between text and image. + + Example: + + ```py + pipeline.set_ip_adapter_scale(0.5) + ``` + """ + unet = getattr(self, self.unet_name) if not hasattr(self, "unet") else self.unet + for attn_processor in unet.attn_processors.values(): + if isinstance(attn_processor, (IPAdapterAttnProcessor, IPAdapterAttnProcessor2_0)): + if not isinstance(scale, list): + scale = [scale] * len(attn_processor.scale) + if len(attn_processor.scale) != len(scale): + raise ValueError( + f"`scale` should be a list of same length as the number if ip-adapters " + f"Expected {len(attn_processor.scale)} but got {len(scale)}." + ) + attn_processor.scale = scale + + def unload_ip_adapter(self): + """ + Unloads the IP Adapter weights + + Examples: + + ```python + >>> # Assuming `pipeline` is already loaded with the IP Adapter weights. + >>> pipeline.unload_ip_adapter() + >>> ... + ``` + """ + # remove CLIP image encoder + if hasattr(self, "image_encoder") and getattr(self, "image_encoder", None) is not None: + self.image_encoder = None + self.register_to_config(image_encoder=[None, None]) + + # remove feature extractor only when safety_checker is None as safety_checker uses + # the feature_extractor later + if not hasattr(self, "safety_checker"): + if hasattr(self, "feature_extractor") and getattr(self, "feature_extractor", None) is not None: + self.feature_extractor = None + self.register_to_config(feature_extractor=[None, None]) + + # remove hidden encoder + self.unet.encoder_hid_proj = None + self.config.encoder_hid_dim_type = None + + # restore original Unet attention processors layers + self.unet.set_default_attn_processor() + + +class VPAdapterMixin: + """Mixin for handling IP Adapters.""" + + @validate_hf_hub_args + def load_ip_adapter( + self, + pretrained_model_name_or_path_or_dict: Union[str, List[str], Dict[str, torch.Tensor]], + subfolder: Union[str, List[str]], + weight_name: Union[str, List[str]], + image_encoder_folder: Optional[str] = "image_encoder", + **kwargs, + ): + """ + Parameters: + pretrained_model_name_or_path_or_dict (`str` or `List[str]` or `os.PathLike` or `List[os.PathLike]` or `dict` or `List[dict]`): + Can be either: + + - A string, the *model id* (for example `google/ddpm-celebahq-256`) of a pretrained model hosted on + the Hub. + - A path to a *directory* (for example `./my_model_directory`) containing the model weights saved + with [`ModelMixin.save_pretrained`]. + - A [torch state + dict](https://pytorch.org/tutorials/beginner/saving_loading_models.html#what-is-a-state-dict). + subfolder (`str` or `List[str]`): + The subfolder location of a model file within a larger model repository on the Hub or locally. + If a list is passed, it should have the same length as `weight_name`. + weight_name (`str` or `List[str]`): + The name of the weight file to load. If a list is passed, it should have the same length as + `weight_name`. + image_encoder_folder (`str`, *optional*, defaults to `image_encoder`): + The subfolder location of the image encoder within a larger model repository on the Hub or locally. + Pass `None` to not load the image encoder. If the image encoder is located in a folder inside `subfolder`, + you only need to pass the name of the folder that contains image encoder weights, e.g. `image_encoder_folder="image_encoder"`. + If the image encoder is located in a folder other than `subfolder`, you should pass the path to the folder that contains image encoder weights, + for example, `image_encoder_folder="different_subfolder/image_encoder"`. + cache_dir (`Union[str, os.PathLike]`, *optional*): + Path to a directory where a downloaded pretrained model configuration is cached if the standard cache + is not used. + force_download (`bool`, *optional*, defaults to `False`): + Whether or not to force the (re-)download of the model weights and configuration files, overriding the + cached versions if they exist. + resume_download (`bool`, *optional*, defaults to `False`): + Whether or not to resume downloading the model weights and configuration files. If set to `False`, any + incompletely downloaded files are deleted. + proxies (`Dict[str, str]`, *optional*): + A dictionary of proxy servers to use by protocol or endpoint, for example, `{'http': 'foo.bar:3128', + 'http://hostname': 'foo.bar:4012'}`. The proxies are used on each request. + local_files_only (`bool`, *optional*, defaults to `False`): + Whether to only load local model weights and configuration files or not. If set to `True`, the model + won't be downloaded from the Hub. + token (`str` or *bool*, *optional*): + The token to use as HTTP bearer authorization for remote files. If `True`, the token generated from + `diffusers-cli login` (stored in `~/.huggingface`) is used. + revision (`str`, *optional*, defaults to `"main"`): + The specific model version to use. It can be a branch name, a tag name, a commit id, or any identifier + allowed by Git. + low_cpu_mem_usage (`bool`, *optional*, defaults to `True` if torch version >= 1.9.0 else `False`): + Speed up model loading only loading the pretrained weights and not initializing the weights. This also + tries to not use more than 1x model size in CPU memory (including peak memory) while loading the model. + Only supported for PyTorch >= 1.9.0. If you are using an older version of PyTorch, setting this + argument to `True` will raise an error. + """ + + # handle the list inputs for multiple IP Adapters + if not isinstance(weight_name, list): + weight_name = [weight_name] + + if not isinstance(pretrained_model_name_or_path_or_dict, list): + pretrained_model_name_or_path_or_dict = [pretrained_model_name_or_path_or_dict] + if len(pretrained_model_name_or_path_or_dict) == 1: + pretrained_model_name_or_path_or_dict = pretrained_model_name_or_path_or_dict * len(weight_name) + + if not isinstance(subfolder, list): + subfolder = [subfolder] + if len(subfolder) == 1: + subfolder = subfolder * len(weight_name) + + if len(weight_name) != len(pretrained_model_name_or_path_or_dict): + raise ValueError("`weight_name` and `pretrained_model_name_or_path_or_dict` must have the same length.") + + if len(weight_name) != len(subfolder): + raise ValueError("`weight_name` and `subfolder` must have the same length.") + + # Load the main state dict first. + cache_dir = kwargs.pop("cache_dir", None) + force_download = kwargs.pop("force_download", False) + resume_download = kwargs.pop("resume_download", False) + proxies = kwargs.pop("proxies", None) + local_files_only = kwargs.pop("local_files_only", None) + token = kwargs.pop("token", None) + revision = kwargs.pop("revision", None) + low_cpu_mem_usage = kwargs.pop("low_cpu_mem_usage", _LOW_CPU_MEM_USAGE_DEFAULT) + + if low_cpu_mem_usage and not is_accelerate_available(): + low_cpu_mem_usage = False + logger.warning( + "Cannot initialize model with low cpu memory usage because `accelerate` was not found in the" + " environment. Defaulting to `low_cpu_mem_usage=False`. It is strongly recommended to install" + " `accelerate` for faster and less memory-intense model loading. You can do so with: \n```\npip" + " install accelerate\n```\n." + ) + + if low_cpu_mem_usage is True and not is_torch_version(">=", "1.9.0"): + raise NotImplementedError( + "Low memory initialization requires torch >= 1.9.0. Please either update your PyTorch version or set" + " `low_cpu_mem_usage=False`." + ) + + user_agent = { + "file_type": "attn_procs_weights", + "framework": "pytorch", + } + state_dicts = [] + for pretrained_model_name_or_path_or_dict, weight_name, subfolder in zip( + pretrained_model_name_or_path_or_dict, weight_name, subfolder + ): + if not isinstance(pretrained_model_name_or_path_or_dict, dict): + model_file = _get_model_file( + pretrained_model_name_or_path_or_dict, + weights_name=weight_name, + cache_dir=cache_dir, + force_download=force_download, + resume_download=resume_download, + proxies=proxies, + local_files_only=local_files_only, + token=token, + revision=revision, + subfolder=subfolder, + user_agent=user_agent, + ) + if weight_name.endswith(".safetensors"): + state_dict = {"image_proj": {}, "ip_adapter": {}} + with safe_open(model_file, framework="pt", device="cpu") as f: + for key in f.keys(): + if key.startswith("image_proj."): + state_dict["image_proj"][key.replace("image_proj.", "")] = f.get_tensor(key) + elif key.startswith("ip_adapter."): + state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = f.get_tensor(key) + else: + state_dict = torch.load(model_file, map_location="cpu") + else: + state_dict = pretrained_model_name_or_path_or_dict + + keys = list(state_dict.keys()) + if keys != ["image_proj", "ip_adapter"]: + raise ValueError("Required keys are (`image_proj` and `ip_adapter`) missing from the state dict.") + + state_dicts.append(state_dict) + + # load CLIP image encoder here if it has not been registered to the pipeline yet + if hasattr(self, "image_encoder") and getattr(self, "image_encoder", None) is None: + if image_encoder_folder is not None: + if not isinstance(pretrained_model_name_or_path_or_dict, dict): + logger.info(f"loading image_encoder from {pretrained_model_name_or_path_or_dict}") + if image_encoder_folder.count("/") == 0: + image_encoder_subfolder = Path(subfolder, image_encoder_folder).as_posix() + else: + image_encoder_subfolder = Path(image_encoder_folder).as_posix() + + image_encoder = CLIPVisionModelWithProjection.from_pretrained( + pretrained_model_name_or_path_or_dict, + subfolder=image_encoder_subfolder, + low_cpu_mem_usage=low_cpu_mem_usage, + ).to(self.device, dtype=self.dtype) + self.register_modules(image_encoder=image_encoder) + else: + raise ValueError( + "`image_encoder` cannot be loaded because `pretrained_model_name_or_path_or_dict` is a state dict." + ) + else: + logger.warning( + "image_encoder is not loaded since `image_encoder_folder=None` passed. You will not be able to use `ip_adapter_image` when calling the pipeline with IP-Adapter." + "Use `ip_adapter_image_embeds` to pass pre-generated image embedding instead." + ) + + # create feature extractor if it has not been registered to the pipeline yet + if hasattr(self, "feature_extractor") and getattr(self, "feature_extractor", None) is None: + feature_extractor = CLIPImageProcessor() + self.register_modules(feature_extractor=feature_extractor) + + # load ip-adapter into unet + unet = getattr(self, self.unet_name) if not hasattr(self, "unet") else self.unet + unet._load_ip_adapter_weights_VPAdapter(state_dicts) + + def set_ip_adapter_scale(self, scale): + """ + Sets the conditioning scale between text and image. + + Example: + + ```py + pipeline.set_ip_adapter_scale(0.5) + ``` + """ + unet = getattr(self, self.unet_name) if not hasattr(self, "unet") else self.unet + for attn_processor in unet.attn_processors.values(): + if isinstance(attn_processor, (IPAdapterAttnProcessor, VPTemporalAdapterAttnProcessor2_0)): + if not isinstance(scale, list): + scale = [scale] * len(attn_processor.scale) + if len(attn_processor.scale) != len(scale): + raise ValueError( + f"`scale` should be a list of same length as the number if ip-adapters " + f"Expected {len(attn_processor.scale)} but got {len(scale)}." + ) + attn_processor.scale = scale + + def unload_ip_adapter(self): + """ + Unloads the IP Adapter weights + + Examples: + + ```python + >>> # Assuming `pipeline` is already loaded with the IP Adapter weights. + >>> pipeline.unload_ip_adapter() + >>> ... + ``` + """ + # remove CLIP image encoder + if hasattr(self, "image_encoder") and getattr(self, "image_encoder", None) is not None: + self.image_encoder = None + self.register_to_config(image_encoder=[None, None]) + + # remove feature extractor only when safety_checker is None as safety_checker uses + # the feature_extractor later + if not hasattr(self, "safety_checker"): + if hasattr(self, "feature_extractor") and getattr(self, "feature_extractor", None) is not None: + self.feature_extractor = None + self.register_to_config(feature_extractor=[None, None]) + + # remove hidden encoder + self.unet.encoder_hid_proj = None + self.config.encoder_hid_dim_type = None + + # restore original Unet attention processors layers + self.unet.set_default_attn_processor() diff --git a/foleycrafter/models/auffusion/loaders/unet.py b/foleycrafter/models/auffusion/loaders/unet.py new file mode 100644 index 0000000000000000000000000000000000000000..c6ab346cb819ab59126ddffc18a548dae9242063 --- /dev/null +++ b/foleycrafter/models/auffusion/loaders/unet.py @@ -0,0 +1,1100 @@ +# Copyright 2023 The HuggingFace 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 +# limitations under the License. +import inspect +import os +from collections import defaultdict +from contextlib import nullcontext +from functools import partial +from typing import Callable, Dict, List, Optional, Union, Tuple + +import safetensors +import torch +import torch.nn.functional as F +from huggingface_hub.utils import validate_hf_hub_args +from torch import nn + +from diffusers.models.embeddings import ImageProjection, MLPProjection, Resampler +from diffusers.models.modeling_utils import _LOW_CPU_MEM_USAGE_DEFAULT, load_model_dict_into_meta +from diffusers.utils import ( + USE_PEFT_BACKEND, + _get_model_file, + delete_adapter_layers, + is_accelerate_available, + logging, + is_torch_version, + set_adapter_layers, + set_weights_and_activate_adapters, +) +from diffusers.loaders.utils import AttnProcsLayers + +from foleycrafter.models.adapters.ip_adapter import VideoProjModel +from foleycrafter.models.auffusion.attention_processor import IPAdapterAttnProcessor2_0, VPTemporalAdapterAttnProcessor2_0, AttnProcessor2_0 + + +if is_accelerate_available(): + from accelerate import init_empty_weights + from accelerate.hooks import AlignDevicesHook, CpuOffload, remove_hook_from_module + +logger = logging.get_logger(__name__) + +class VPAdapterImageProjection(nn.Module): + def __init__(self, IPAdapterImageProjectionLayers: Union[List[nn.Module], Tuple[nn.Module]]): + super().__init__() + self.image_projection_layers = nn.ModuleList(IPAdapterImageProjectionLayers) + + def forward(self, image_embeds: List[torch.FloatTensor]): + projected_image_embeds = [] + + # currently, we accept `image_embeds` as + # 1. a tensor (deprecated) with shape [batch_size, embed_dim] or [batch_size, sequence_length, embed_dim] + # 2. list of `n` tensors where `n` is number of ip-adapters, each tensor can hae shape [batch_size, num_images, embed_dim] or [batch_size, num_images, sequence_length, embed_dim] + if not isinstance(image_embeds, list): + deprecation_message = ( + "You have passed a tensor as `image_embeds`.This is deprecated and will be removed in a future release." + " Please make sure to update your script to pass `image_embeds` as a list of tensors to supress this warning." + ) + image_embeds = [image_embeds.unsqueeze(1)] + + if len(image_embeds) != len(self.image_projection_layers): + raise ValueError( + f"image_embeds must have the same length as image_projection_layers, got {len(image_embeds)} and {len(self.image_projection_layers)}" + ) + + for image_embed, image_projection_layer in zip(image_embeds, self.image_projection_layers): + image_embed = image_embed.squeeze(1) + batch_size, num_images = image_embed.shape[0], image_embed.shape[1] + image_embed = image_embed.reshape((batch_size * num_images,) + image_embed.shape[2:]) + image_embed = image_projection_layer(image_embed) + image_embed = image_embed.reshape((batch_size, num_images) + image_embed.shape[1:]) + + projected_image_embeds.append(image_embed) + + return projected_image_embeds + +class MultiIPAdapterImageProjection(nn.Module): + def __init__(self, IPAdapterImageProjectionLayers: Union[List[nn.Module], Tuple[nn.Module]]): + super().__init__() + self.image_projection_layers = nn.ModuleList(IPAdapterImageProjectionLayers) + + def forward(self, image_embeds: List[torch.FloatTensor]): + projected_image_embeds = [] + + # currently, we accept `image_embeds` as + # 1. a tensor (deprecated) with shape [batch_size, embed_dim] or [batch_size, sequence_length, embed_dim] + # 2. list of `n` tensors where `n` is number of ip-adapters, each tensor can hae shape [batch_size, num_images, embed_dim] or [batch_size, num_images, sequence_length, embed_dim] + if not isinstance(image_embeds, list): + deprecation_message = ( + "You have passed a tensor as `image_embeds`.This is deprecated and will be removed in a future release." + " Please make sure to update your script to pass `image_embeds` as a list of tensors to supress this warning." + ) + image_embeds = [image_embeds.unsqueeze(1)] + + if len(image_embeds) != len(self.image_projection_layers): + raise ValueError( + f"image_embeds must have the same length as image_projection_layers, got {len(image_embeds)} and {len(self.image_projection_layers)}" + ) + + for image_embed, image_projection_layer in zip(image_embeds, self.image_projection_layers): + batch_size, num_images = image_embed.shape[0], image_embed.shape[1] + image_embed = image_embed.reshape((batch_size * num_images,) + image_embed.shape[2:]) + image_embed = image_projection_layer(image_embed) + image_embed = image_embed.reshape((batch_size, num_images) + image_embed.shape[1:]) + + projected_image_embeds.append(image_embed) + + return projected_image_embeds + + +TEXT_ENCODER_NAME = "text_encoder" +UNET_NAME = "unet" + +LORA_WEIGHT_NAME = "pytorch_lora_weights.bin" +LORA_WEIGHT_NAME_SAFE = "pytorch_lora_weights.safetensors" + +CUSTOM_DIFFUSION_WEIGHT_NAME = "pytorch_custom_diffusion_weights.bin" +CUSTOM_DIFFUSION_WEIGHT_NAME_SAFE = "pytorch_custom_diffusion_weights.safetensors" + + +class UNet2DConditionLoadersMixin: + """ + Load LoRA layers into a [`UNet2DCondtionModel`]. + """ + + text_encoder_name = TEXT_ENCODER_NAME + unet_name = UNET_NAME + + @validate_hf_hub_args + def load_attn_procs(self, pretrained_model_name_or_path_or_dict: Union[str, Dict[str, torch.Tensor]], **kwargs): + r""" + Load pretrained attention processor layers into [`UNet2DConditionModel`]. Attention processor layers have to be + defined in + [`attention_processor.py`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py) + and be a `torch.nn.Module` class. + + Parameters: + pretrained_model_name_or_path_or_dict (`str` or `os.PathLike` or `dict`): + Can be either: + + - A string, the model id (for example `google/ddpm-celebahq-256`) of a pretrained model hosted on + the Hub. + - A path to a directory (for example `./my_model_directory`) containing the model weights saved + with [`ModelMixin.save_pretrained`]. + - A [torch state + dict](https://pytorch.org/tutorials/beginner/saving_loading_models.html#what-is-a-state-dict). + + cache_dir (`Union[str, os.PathLike]`, *optional*): + Path to a directory where a downloaded pretrained model configuration is cached if the standard cache + is not used. + force_download (`bool`, *optional*, defaults to `False`): + Whether or not to force the (re-)download of the model weights and configuration files, overriding the + cached versions if they exist. + resume_download (`bool`, *optional*, defaults to `False`): + Whether or not to resume downloading the model weights and configuration files. If set to `False`, any + incompletely downloaded files are deleted. + proxies (`Dict[str, str]`, *optional*): + A dictionary of proxy servers to use by protocol or endpoint, for example, `{'http': 'foo.bar:3128', + 'http://hostname': 'foo.bar:4012'}`. The proxies are used on each request. + local_files_only (`bool`, *optional*, defaults to `False`): + Whether to only load local model weights and configuration files or not. If set to `True`, the model + won't be downloaded from the Hub. + token (`str` or *bool*, *optional*): + The token to use as HTTP bearer authorization for remote files. If `True`, the token generated from + `diffusers-cli login` (stored in `~/.huggingface`) is used. + low_cpu_mem_usage (`bool`, *optional*, defaults to `True` if torch version >= 1.9.0 else `False`): + Speed up model loading only loading the pretrained weights and not initializing the weights. This also + tries to not use more than 1x model size in CPU memory (including peak memory) while loading the model. + Only supported for PyTorch >= 1.9.0. If you are using an older version of PyTorch, setting this + argument to `True` will raise an error. + revision (`str`, *optional*, defaults to `"main"`): + The specific model version to use. It can be a branch name, a tag name, a commit id, or any identifier + allowed by Git. + subfolder (`str`, *optional*, defaults to `""`): + The subfolder location of a model file within a larger model repository on the Hub or locally. + mirror (`str`, *optional*): + Mirror source to resolve accessibility issues if you’re downloading a model in China. We do not + guarantee the timeliness or safety of the source, and you should refer to the mirror site for more + information. + + Example: + + ```py + from diffusers import AutoPipelineForText2Image + import torch + + pipeline = AutoPipelineForText2Image.from_pretrained( + "stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16 + ).to("cuda") + pipeline.unet.load_attn_procs( + "jbilcke-hf/sdxl-cinematic-1", weight_name="pytorch_lora_weights.safetensors", adapter_name="cinematic" + ) + ``` + """ + from diffusers.models.attention_processor import CustomDiffusionAttnProcessor + from diffusers.models.lora import LoRACompatibleConv, LoRACompatibleLinear, LoRAConv2dLayer, LoRALinearLayer + + cache_dir = kwargs.pop("cache_dir", None) + force_download = kwargs.pop("force_download", False) + resume_download = kwargs.pop("resume_download", False) + proxies = kwargs.pop("proxies", None) + local_files_only = kwargs.pop("local_files_only", None) + token = kwargs.pop("token", None) + revision = kwargs.pop("revision", None) + subfolder = kwargs.pop("subfolder", None) + weight_name = kwargs.pop("weight_name", None) + use_safetensors = kwargs.pop("use_safetensors", None) + low_cpu_mem_usage = kwargs.pop("low_cpu_mem_usage", _LOW_CPU_MEM_USAGE_DEFAULT) + # This value has the same meaning as the `--network_alpha` option in the kohya-ss trainer script. + # See https://github.com/darkstorm2150/sd-scripts/blob/main/docs/train_network_README-en.md#execute-learning + network_alphas = kwargs.pop("network_alphas", None) + + _pipeline = kwargs.pop("_pipeline", None) + + is_network_alphas_none = network_alphas is None + + allow_pickle = False + + if use_safetensors is None: + use_safetensors = True + allow_pickle = True + + user_agent = { + "file_type": "attn_procs_weights", + "framework": "pytorch", + } + + if low_cpu_mem_usage and not is_accelerate_available(): + low_cpu_mem_usage = False + logger.warning( + "Cannot initialize model with low cpu memory usage because `accelerate` was not found in the" + " environment. Defaulting to `low_cpu_mem_usage=False`. It is strongly recommended to install" + " `accelerate` for faster and less memory-intense model loading. You can do so with: \n```\npip" + " install accelerate\n```\n." + ) + + model_file = None + if not isinstance(pretrained_model_name_or_path_or_dict, dict): + # Let's first try to load .safetensors weights + if (use_safetensors and weight_name is None) or ( + weight_name is not None and weight_name.endswith(".safetensors") + ): + try: + model_file = _get_model_file( + pretrained_model_name_or_path_or_dict, + weights_name=weight_name or LORA_WEIGHT_NAME_SAFE, + cache_dir=cache_dir, + force_download=force_download, + resume_download=resume_download, + proxies=proxies, + local_files_only=local_files_only, + token=token, + revision=revision, + subfolder=subfolder, + user_agent=user_agent, + ) + state_dict = safetensors.torch.load_file(model_file, device="cpu") + except IOError as e: + if not allow_pickle: + raise e + # try loading non-safetensors weights + pass + if model_file is None: + model_file = _get_model_file( + pretrained_model_name_or_path_or_dict, + weights_name=weight_name or LORA_WEIGHT_NAME, + cache_dir=cache_dir, + force_download=force_download, + resume_download=resume_download, + proxies=proxies, + local_files_only=local_files_only, + token=token, + revision=revision, + subfolder=subfolder, + user_agent=user_agent, + ) + state_dict = torch.load(model_file, map_location="cpu") + else: + state_dict = pretrained_model_name_or_path_or_dict + + # fill attn processors + lora_layers_list = [] + + is_lora = all(("lora" in k or k.endswith(".alpha")) for k in state_dict.keys()) and not USE_PEFT_BACKEND + is_custom_diffusion = any("custom_diffusion" in k for k in state_dict.keys()) + + if is_lora: + # correct keys + state_dict, network_alphas = self.convert_state_dict_legacy_attn_format(state_dict, network_alphas) + + if network_alphas is not None: + network_alphas_keys = list(network_alphas.keys()) + used_network_alphas_keys = set() + + lora_grouped_dict = defaultdict(dict) + mapped_network_alphas = {} + + all_keys = list(state_dict.keys()) + for key in all_keys: + value = state_dict.pop(key) + attn_processor_key, sub_key = ".".join(key.split(".")[:-3]), ".".join(key.split(".")[-3:]) + lora_grouped_dict[attn_processor_key][sub_key] = value + + # Create another `mapped_network_alphas` dictionary so that we can properly map them. + if network_alphas is not None: + for k in network_alphas_keys: + if k.replace(".alpha", "") in key: + mapped_network_alphas.update({attn_processor_key: network_alphas.get(k)}) + used_network_alphas_keys.add(k) + + if not is_network_alphas_none: + if len(set(network_alphas_keys) - used_network_alphas_keys) > 0: + raise ValueError( + f"The `network_alphas` has to be empty at this point but has the following keys \n\n {', '.join(network_alphas.keys())}" + ) + + if len(state_dict) > 0: + raise ValueError( + f"The `state_dict` has to be empty at this point but has the following keys \n\n {', '.join(state_dict.keys())}" + ) + + for key, value_dict in lora_grouped_dict.items(): + attn_processor = self + for sub_key in key.split("."): + attn_processor = getattr(attn_processor, sub_key) + + # Process non-attention layers, which don't have to_{k,v,q,out_proj}_lora layers + # or add_{k,v,q,out_proj}_proj_lora layers. + rank = value_dict["lora.down.weight"].shape[0] + + if isinstance(attn_processor, LoRACompatibleConv): + in_features = attn_processor.in_channels + out_features = attn_processor.out_channels + kernel_size = attn_processor.kernel_size + + ctx = init_empty_weights if low_cpu_mem_usage else nullcontext + with ctx(): + lora = LoRAConv2dLayer( + in_features=in_features, + out_features=out_features, + rank=rank, + kernel_size=kernel_size, + stride=attn_processor.stride, + padding=attn_processor.padding, + network_alpha=mapped_network_alphas.get(key), + ) + elif isinstance(attn_processor, LoRACompatibleLinear): + ctx = init_empty_weights if low_cpu_mem_usage else nullcontext + with ctx(): + lora = LoRALinearLayer( + attn_processor.in_features, + attn_processor.out_features, + rank, + mapped_network_alphas.get(key), + ) + else: + raise ValueError(f"Module {key} is not a LoRACompatibleConv or LoRACompatibleLinear module.") + + value_dict = {k.replace("lora.", ""): v for k, v in value_dict.items()} + lora_layers_list.append((attn_processor, lora)) + + if low_cpu_mem_usage: + device = next(iter(value_dict.values())).device + dtype = next(iter(value_dict.values())).dtype + load_model_dict_into_meta(lora, value_dict, device=device, dtype=dtype) + else: + lora.load_state_dict(value_dict) + + elif is_custom_diffusion: + attn_processors = {} + custom_diffusion_grouped_dict = defaultdict(dict) + for key, value in state_dict.items(): + if len(value) == 0: + custom_diffusion_grouped_dict[key] = {} + else: + if "to_out" in key: + attn_processor_key, sub_key = ".".join(key.split(".")[:-3]), ".".join(key.split(".")[-3:]) + else: + attn_processor_key, sub_key = ".".join(key.split(".")[:-2]), ".".join(key.split(".")[-2:]) + custom_diffusion_grouped_dict[attn_processor_key][sub_key] = value + + for key, value_dict in custom_diffusion_grouped_dict.items(): + if len(value_dict) == 0: + attn_processors[key] = CustomDiffusionAttnProcessor( + train_kv=False, train_q_out=False, hidden_size=None, cross_attention_dim=None + ) + else: + cross_attention_dim = value_dict["to_k_custom_diffusion.weight"].shape[1] + hidden_size = value_dict["to_k_custom_diffusion.weight"].shape[0] + train_q_out = True if "to_q_custom_diffusion.weight" in value_dict else False + attn_processors[key] = CustomDiffusionAttnProcessor( + train_kv=True, + train_q_out=train_q_out, + hidden_size=hidden_size, + cross_attention_dim=cross_attention_dim, + ) + attn_processors[key].load_state_dict(value_dict) + elif USE_PEFT_BACKEND: + # In that case we have nothing to do as loading the adapter weights is already handled above by `set_peft_model_state_dict` + # on the Unet + pass + else: + raise ValueError( + f"{model_file} does not seem to be in the correct format expected by LoRA or Custom Diffusion training." + ) + + # + + def convert_state_dict_legacy_attn_format(self, state_dict, network_alphas): + is_new_lora_format = all( + key.startswith(self.unet_name) or key.startswith(self.text_encoder_name) for key in state_dict.keys() + ) + if is_new_lora_format: + # Strip the `"unet"` prefix. + is_text_encoder_present = any(key.startswith(self.text_encoder_name) for key in state_dict.keys()) + if is_text_encoder_present: + warn_message = "The state_dict contains LoRA params corresponding to the text encoder which are not being used here. To use both UNet and text encoder related LoRA params, use [`pipe.load_lora_weights()`](https://huggingface.co/docs/diffusers/main/en/api/loaders#diffusers.loaders.LoraLoaderMixin.load_lora_weights)." + logger.warn(warn_message) + unet_keys = [k for k in state_dict.keys() if k.startswith(self.unet_name)] + state_dict = {k.replace(f"{self.unet_name}.", ""): v for k, v in state_dict.items() if k in unet_keys} + + # change processor format to 'pure' LoRACompatibleLinear format + if any("processor" in k.split(".") for k in state_dict.keys()): + + def format_to_lora_compatible(key): + if "processor" not in key.split("."): + return key + return key.replace(".processor", "").replace("to_out_lora", "to_out.0.lora").replace("_lora", ".lora") + + state_dict = {format_to_lora_compatible(k): v for k, v in state_dict.items()} + + if network_alphas is not None: + network_alphas = {format_to_lora_compatible(k): v for k, v in network_alphas.items()} + return state_dict, network_alphas + + def save_attn_procs( + self, + save_directory: Union[str, os.PathLike], + is_main_process: bool = True, + weight_name: str = None, + save_function: Callable = None, + safe_serialization: bool = True, + **kwargs, + ): + r""" + Save attention processor layers to a directory so that it can be reloaded with the + [`~loaders.UNet2DConditionLoadersMixin.load_attn_procs`] method. + + Arguments: + save_directory (`str` or `os.PathLike`): + Directory to save an attention processor to (will be created if it doesn't exist). + is_main_process (`bool`, *optional*, defaults to `True`): + Whether the process calling this is the main process or not. Useful during distributed training and you + need to call this function on all processes. In this case, set `is_main_process=True` only on the main + process to avoid race conditions. + save_function (`Callable`): + The function to use to save the state dictionary. Useful during distributed training when you need to + replace `torch.save` with another method. Can be configured with the environment variable + `DIFFUSERS_SAVE_MODE`. + safe_serialization (`bool`, *optional*, defaults to `True`): + Whether to save the model using `safetensors` or with `pickle`. + + Example: + + ```py + import torch + from diffusers import DiffusionPipeline + + pipeline = DiffusionPipeline.from_pretrained( + "CompVis/stable-diffusion-v1-4", + torch_dtype=torch.float16, + ).to("cuda") + pipeline.unet.load_attn_procs("path-to-save-model", weight_name="pytorch_custom_diffusion_weights.bin") + pipeline.unet.save_attn_procs("path-to-save-model", weight_name="pytorch_custom_diffusion_weights.bin") + ``` + """ + from diffusers.models.attention_processor import ( + CustomDiffusionAttnProcessor, + CustomDiffusionAttnProcessor2_0, + CustomDiffusionXFormersAttnProcessor, + ) + + if os.path.isfile(save_directory): + logger.error(f"Provided path ({save_directory}) should be a directory, not a file") + return + + if save_function is None: + if safe_serialization: + + def save_function(weights, filename): + return safetensors.torch.save_file(weights, filename, metadata={"format": "pt"}) + + else: + save_function = torch.save + + os.makedirs(save_directory, exist_ok=True) + + is_custom_diffusion = any( + isinstance( + x, + (CustomDiffusionAttnProcessor, CustomDiffusionAttnProcessor2_0, CustomDiffusionXFormersAttnProcessor), + ) + for (_, x) in self.attn_processors.items() + ) + if is_custom_diffusion: + model_to_save = AttnProcsLayers( + { + y: x + for (y, x) in self.attn_processors.items() + if isinstance( + x, + ( + CustomDiffusionAttnProcessor, + CustomDiffusionAttnProcessor2_0, + CustomDiffusionXFormersAttnProcessor, + ), + ) + } + ) + state_dict = model_to_save.state_dict() + for name, attn in self.attn_processors.items(): + if len(attn.state_dict()) == 0: + state_dict[name] = {} + else: + model_to_save = AttnProcsLayers(self.attn_processors) + state_dict = model_to_save.state_dict() + + if weight_name is None: + if safe_serialization: + weight_name = CUSTOM_DIFFUSION_WEIGHT_NAME_SAFE if is_custom_diffusion else LORA_WEIGHT_NAME_SAFE + else: + weight_name = CUSTOM_DIFFUSION_WEIGHT_NAME if is_custom_diffusion else LORA_WEIGHT_NAME + + # Save the model + save_function(state_dict, os.path.join(save_directory, weight_name)) + logger.info(f"Model weights saved in {os.path.join(save_directory, weight_name)}") + + def fuse_lora(self, lora_scale=1.0, safe_fusing=False, adapter_names=None): + self.lora_scale = lora_scale + self._safe_fusing = safe_fusing + self.apply(partial(self._fuse_lora_apply, adapter_names=adapter_names)) + + def _fuse_lora_apply(self, module, adapter_names=None): + if not USE_PEFT_BACKEND: + if hasattr(module, "_fuse_lora"): + module._fuse_lora(self.lora_scale, self._safe_fusing) + + if adapter_names is not None: + raise ValueError( + "The `adapter_names` argument is not supported in your environment. Please switch" + " to PEFT backend to use this argument by installing latest PEFT and transformers." + " `pip install -U peft transformers`" + ) + else: + from peft.tuners.tuners_utils import BaseTunerLayer + + merge_kwargs = {"safe_merge": self._safe_fusing} + + if isinstance(module, BaseTunerLayer): + if self.lora_scale != 1.0: + module.scale_layer(self.lora_scale) + + # For BC with prevous PEFT versions, we need to check the signature + # of the `merge` method to see if it supports the `adapter_names` argument. + supported_merge_kwargs = list(inspect.signature(module.merge).parameters) + if "adapter_names" in supported_merge_kwargs: + merge_kwargs["adapter_names"] = adapter_names + elif "adapter_names" not in supported_merge_kwargs and adapter_names is not None: + raise ValueError( + "The `adapter_names` argument is not supported with your PEFT version. Please upgrade" + " to the latest version of PEFT. `pip install -U peft`" + ) + + module.merge(**merge_kwargs) + + def unfuse_lora(self): + self.apply(self._unfuse_lora_apply) + + def _unfuse_lora_apply(self, module): + if not USE_PEFT_BACKEND: + if hasattr(module, "_unfuse_lora"): + module._unfuse_lora() + else: + from peft.tuners.tuners_utils import BaseTunerLayer + + if isinstance(module, BaseTunerLayer): + module.unmerge() + + def set_adapters( + self, + adapter_names: Union[List[str], str], + weights: Optional[Union[List[float], float]] = None, + ): + """ + Set the currently active adapters for use in the UNet. + + Args: + adapter_names (`List[str]` or `str`): + The names of the adapters to use. + adapter_weights (`Union[List[float], float]`, *optional*): + The adapter(s) weights to use with the UNet. If `None`, the weights are set to `1.0` for all the + adapters. + + Example: + + ```py + from diffusers import AutoPipelineForText2Image + import torch + + pipeline = AutoPipelineForText2Image.from_pretrained( + "stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16 + ).to("cuda") + pipeline.load_lora_weights( + "jbilcke-hf/sdxl-cinematic-1", weight_name="pytorch_lora_weights.safetensors", adapter_name="cinematic" + ) + pipeline.load_lora_weights("nerijs/pixel-art-xl", weight_name="pixel-art-xl.safetensors", adapter_name="pixel") + pipeline.set_adapters(["cinematic", "pixel"], adapter_weights=[0.5, 0.5]) + ``` + """ + if not USE_PEFT_BACKEND: + raise ValueError("PEFT backend is required for `set_adapters()`.") + + adapter_names = [adapter_names] if isinstance(adapter_names, str) else adapter_names + + if weights is None: + weights = [1.0] * len(adapter_names) + elif isinstance(weights, float): + weights = [weights] * len(adapter_names) + + if len(adapter_names) != len(weights): + raise ValueError( + f"Length of adapter names {len(adapter_names)} is not equal to the length of their weights {len(weights)}." + ) + + set_weights_and_activate_adapters(self, adapter_names, weights) + + def disable_lora(self): + """ + Disable the UNet's active LoRA layers. + + Example: + + ```py + from diffusers import AutoPipelineForText2Image + import torch + + pipeline = AutoPipelineForText2Image.from_pretrained( + "stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16 + ).to("cuda") + pipeline.load_lora_weights( + "jbilcke-hf/sdxl-cinematic-1", weight_name="pytorch_lora_weights.safetensors", adapter_name="cinematic" + ) + pipeline.disable_lora() + ``` + """ + if not USE_PEFT_BACKEND: + raise ValueError("PEFT backend is required for this method.") + set_adapter_layers(self, enabled=False) + + def enable_lora(self): + """ + Enable the UNet's active LoRA layers. + + Example: + + ```py + from diffusers import AutoPipelineForText2Image + import torch + + pipeline = AutoPipelineForText2Image.from_pretrained( + "stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16 + ).to("cuda") + pipeline.load_lora_weights( + "jbilcke-hf/sdxl-cinematic-1", weight_name="pytorch_lora_weights.safetensors", adapter_name="cinematic" + ) + pipeline.enable_lora() + ``` + """ + if not USE_PEFT_BACKEND: + raise ValueError("PEFT backend is required for this method.") + set_adapter_layers(self, enabled=True) + + def delete_adapters(self, adapter_names: Union[List[str], str]): + """ + Delete an adapter's LoRA layers from the UNet. + + Args: + adapter_names (`Union[List[str], str]`): + The names (single string or list of strings) of the adapter to delete. + + Example: + + ```py + from diffusers import AutoPipelineForText2Image + import torch + + pipeline = AutoPipelineForText2Image.from_pretrained( + "stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16 + ).to("cuda") + pipeline.load_lora_weights( + "jbilcke-hf/sdxl-cinematic-1", weight_name="pytorch_lora_weights.safetensors", adapter_names="cinematic" + ) + pipeline.delete_adapters("cinematic") + ``` + """ + if not USE_PEFT_BACKEND: + raise ValueError("PEFT backend is required for this method.") + + if isinstance(adapter_names, str): + adapter_names = [adapter_names] + + for adapter_name in adapter_names: + delete_adapter_layers(self, adapter_name) + + # Pop also the corresponding adapter from the config + if hasattr(self, "peft_config"): + self.peft_config.pop(adapter_name, None) + + def _convert_ip_adapter_image_proj_to_diffusers(self, state_dict, low_cpu_mem_usage=False): + if low_cpu_mem_usage: + if is_accelerate_available(): + from accelerate import init_empty_weights + + else: + low_cpu_mem_usage = False + logger.warning( + "Cannot initialize model with low cpu memory usage because `accelerate` was not found in the" + " environment. Defaulting to `low_cpu_mem_usage=False`. It is strongly recommended to install" + " `accelerate` for faster and less memory-intense model loading. You can do so with: \n```\npip" + " install accelerate\n```\n." + ) + + if low_cpu_mem_usage is True and not is_torch_version(">=", "1.9.0"): + raise NotImplementedError( + "Low memory initialization requires torch >= 1.9.0. Please either update your PyTorch version or set" + " `low_cpu_mem_usage=False`." + ) + + updated_state_dict = {} + image_projection = None + init_context = init_empty_weights if low_cpu_mem_usage else nullcontext + + if "proj.weight" in state_dict: + # IP-Adapter + num_image_text_embeds = 4 + clip_embeddings_dim = state_dict["proj.weight"].shape[-1] + cross_attention_dim = state_dict["proj.weight"].shape[0] // num_image_text_embeds + + with init_context(): + image_projection = ImageProjection( + cross_attention_dim=cross_attention_dim, + image_embed_dim=clip_embeddings_dim, + num_image_text_embeds=num_image_text_embeds, + ) + + for key, value in state_dict.items(): + diffusers_name = key.replace("proj", "image_embeds") + updated_state_dict[diffusers_name] = value + + if not low_cpu_mem_usage: + image_projection.load_state_dict(updated_state_dict) + else: + load_model_dict_into_meta(image_projection, updated_state_dict, device=self.device, dtype=self.dtype) + + return image_projection + + # def _convert_ip_adapter_image_proj_to_diffusers(self, state_dict, multi_frames_condition): + # updated_state_dict = {} + # image_projection = None + + # if "proj.weight" in state_dict: + # # IP-Adapter + # # NOTE: adapt for multi-frame + # num_image_text_embeds = 4 + # clip_embeddings_dim = state_dict["proj.weight"].shape[-1] + # cross_attention_dim = state_dict["proj.weight"].shape[0] // 4 + # # cross_attention_dim = state_dict["proj.weight"].shape[0] + + # if not multi_frames_condition: + # image_projection = ImageProjection( + # cross_attention_dim=cross_attention_dim, + # image_embed_dim=clip_embeddings_dim, + # num_image_text_embeds=num_image_text_embeds, + # ) + # else: + # num_image_text_embeds = 50 + # cross_attention_dim = state_dict["proj.weight"].shape[0] + # image_projection = VideoProjModel( + # cross_attention_dim=cross_attention_dim, + # clip_embeddings_dim=clip_embeddings_dim, + # clip_extra_context_tokens=1, + # video_frame=num_image_text_embeds, + # ) + + # for key, value in state_dict.items(): + # if not multi_frames_condition: + # diffusers_name = key.replace("proj", "image_embeds") + # else: + # diffusers_name = key + # updated_state_dict[diffusers_name] = value + + # elif "proj.3.weight" in state_dict: + # # IP-Adapter Full + # clip_embeddings_dim = state_dict["proj.0.weight"].shape[0] + # cross_attention_dim = state_dict["proj.3.weight"].shape[0] + + # image_projection = MLPProjection( + # cross_attention_dim=cross_attention_dim, image_embed_dim=clip_embeddings_dim + # ) + + # for key, value in state_dict.items(): + # diffusers_name = key.replace("proj.0", "ff.net.0.proj") + # diffusers_name = diffusers_name.replace("proj.2", "ff.net.2") + # diffusers_name = diffusers_name.replace("proj.3", "norm") + # updated_state_dict[diffusers_name] = value + + # else: + # # IP-Adapter Plus + # num_image_text_embeds = state_dict["latents"].shape[1] + # embed_dims = state_dict["proj_in.weight"].shape[1] + # output_dims = state_dict["proj_out.weight"].shape[0] + # hidden_dims = state_dict["latents"].shape[2] + # heads = state_dict["layers.0.0.to_q.weight"].shape[0] // 64 + + # image_projection = Resampler( + # embed_dims=embed_dims, + # output_dims=output_dims, + # hidden_dims=hidden_dims, + # heads=heads, + # num_queries=num_image_text_embeds, + # ) + + # for key, value in state_dict.items(): + # diffusers_name = key.replace("0.to", "2.to") + # diffusers_name = diffusers_name.replace("1.0.weight", "3.0.weight") + # diffusers_name = diffusers_name.replace("1.0.bias", "3.0.bias") + # diffusers_name = diffusers_name.replace("1.1.weight", "3.1.net.0.proj.weight") + # diffusers_name = diffusers_name.replace("1.3.weight", "3.1.net.2.weight") + + # if "norm1" in diffusers_name: + # updated_state_dict[diffusers_name.replace("0.norm1", "0")] = value + # elif "norm2" in diffusers_name: + # updated_state_dict[diffusers_name.replace("0.norm2", "1")] = value + # elif "to_kv" in diffusers_name: + # v_chunk = value.chunk(2, dim=0) + # updated_state_dict[diffusers_name.replace("to_kv", "to_k")] = v_chunk[0] + # updated_state_dict[diffusers_name.replace("to_kv", "to_v")] = v_chunk[1] + # elif "to_out" in diffusers_name: + # updated_state_dict[diffusers_name.replace("to_out", "to_out.0")] = value + # else: + # updated_state_dict[diffusers_name] = value + + # image_projection.load_state_dict(updated_state_dict) + # return image_projection + + def _convert_ip_adapter_attn_to_diffusers_VPAdapter(self, state_dicts, low_cpu_mem_usage=False): + from diffusers.models.attention_processor import ( + AttnProcessor, + IPAdapterAttnProcessor, + ) + + if low_cpu_mem_usage: + if is_accelerate_available(): + from accelerate import init_empty_weights + + else: + low_cpu_mem_usage = False + logger.warning( + "Cannot initialize model with low cpu memory usage because `accelerate` was not found in the" + " environment. Defaulting to `low_cpu_mem_usage=False`. It is strongly recommended to install" + " `accelerate` for faster and less memory-intense model loading. You can do so with: \n```\npip" + " install accelerate\n```\n." + ) + + if low_cpu_mem_usage is True and not is_torch_version(">=", "1.9.0"): + raise NotImplementedError( + "Low memory initialization requires torch >= 1.9.0. Please either update your PyTorch version or set" + " `low_cpu_mem_usage=False`." + ) + + # set ip-adapter cross-attention processors & load state_dict + attn_procs = {} + key_id = 1 + init_context = init_empty_weights if low_cpu_mem_usage else nullcontext + for name in self.attn_processors.keys(): + cross_attention_dim = None if name.endswith("attn1.processor") else self.config.cross_attention_dim + if name.startswith("mid_block"): + hidden_size = self.config.block_out_channels[-1] + elif name.startswith("up_blocks"): + block_id = int(name[len("up_blocks.")]) + hidden_size = list(reversed(self.config.block_out_channels))[block_id] + elif name.startswith("down_blocks"): + block_id = int(name[len("down_blocks.")]) + hidden_size = self.config.block_out_channels[block_id] + + if cross_attention_dim is None or "motion_modules" in name or 'fuser' in name: + attn_processor_class = ( + AttnProcessor2_0 if hasattr(F, "scaled_dot_product_attention") else AttnProcessor + ) + attn_procs[name] = attn_processor_class() + else: + attn_processor_class = ( + VPTemporalAdapterAttnProcessor2_0 if hasattr(F, "scaled_dot_product_attention") else IPAdapterAttnProcessor + ) + num_image_text_embeds = [] + for state_dict in state_dicts: + if "proj.weight" in state_dict["image_proj"]: + # IP-Adapter + num_image_text_embeds += [4] + elif "proj.3.weight" in state_dict["image_proj"]: + # IP-Adapter Full Face + num_image_text_embeds += [257] # 256 CLIP tokens + 1 CLS token + else: + # IP-Adapter Plus + num_image_text_embeds += [state_dict["image_proj"]["latents"].shape[1]] + + with init_context(): + attn_procs[name] = attn_processor_class( + hidden_size=hidden_size, + cross_attention_dim=cross_attention_dim, + scale=1.0, + num_tokens=num_image_text_embeds, + ) + + value_dict = {} + for i, state_dict in enumerate(state_dicts): + value_dict.update({f"to_k_ip.{i}.weight": state_dict["ip_adapter"][f"{key_id}.to_k_ip.weight"]}) + value_dict.update({f"to_v_ip.{i}.weight": state_dict["ip_adapter"][f"{key_id}.to_v_ip.weight"]}) + + if not low_cpu_mem_usage: + attn_procs[name].load_state_dict(value_dict) + else: + device = next(iter(value_dict.values())).device + dtype = next(iter(value_dict.values())).dtype + load_model_dict_into_meta(attn_procs[name], value_dict, device=device, dtype=dtype) + + key_id += 2 + + return attn_procs + + def _convert_ip_adapter_attn_to_diffusers(self, state_dicts, low_cpu_mem_usage=False): + from diffusers.models.attention_processor import ( + AttnProcessor, + IPAdapterAttnProcessor, + ) + + if low_cpu_mem_usage: + if is_accelerate_available(): + from accelerate import init_empty_weights + + else: + low_cpu_mem_usage = False + logger.warning( + "Cannot initialize model with low cpu memory usage because `accelerate` was not found in the" + " environment. Defaulting to `low_cpu_mem_usage=False`. It is strongly recommended to install" + " `accelerate` for faster and less memory-intense model loading. You can do so with: \n```\npip" + " install accelerate\n```\n." + ) + + if low_cpu_mem_usage is True and not is_torch_version(">=", "1.9.0"): + raise NotImplementedError( + "Low memory initialization requires torch >= 1.9.0. Please either update your PyTorch version or set" + " `low_cpu_mem_usage=False`." + ) + + # set ip-adapter cross-attention processors & load state_dict + attn_procs = {} + key_id = 1 + init_context = init_empty_weights if low_cpu_mem_usage else nullcontext + for name in self.attn_processors.keys(): + cross_attention_dim = None if name.endswith("attn1.processor") else self.config.cross_attention_dim + if name.startswith("mid_block"): + hidden_size = self.config.block_out_channels[-1] + elif name.startswith("up_blocks"): + block_id = int(name[len("up_blocks.")]) + hidden_size = list(reversed(self.config.block_out_channels))[block_id] + elif name.startswith("down_blocks"): + block_id = int(name[len("down_blocks.")]) + hidden_size = self.config.block_out_channels[block_id] + + if cross_attention_dim is None or "motion_modules" in name or 'fuser' in name: + attn_processor_class = ( + AttnProcessor2_0 if hasattr(F, "scaled_dot_product_attention") else AttnProcessor + ) + attn_procs[name] = attn_processor_class() + else: + attn_processor_class = ( + IPAdapterAttnProcessor2_0 if hasattr(F, "scaled_dot_product_attention") else IPAdapterAttnProcessor + ) + num_image_text_embeds = [] + for state_dict in state_dicts: + if "proj.weight" in state_dict["image_proj"]: + # IP-Adapter + num_image_text_embeds += [4] + elif "proj.3.weight" in state_dict["image_proj"]: + # IP-Adapter Full Face + num_image_text_embeds += [257] # 256 CLIP tokens + 1 CLS token + else: + # IP-Adapter Plus + num_image_text_embeds += [state_dict["image_proj"]["latents"].shape[1]] + + with init_context(): + attn_procs[name] = attn_processor_class( + hidden_size=hidden_size, + cross_attention_dim=cross_attention_dim, + scale=1.0, + num_tokens=num_image_text_embeds, + ) + + value_dict = {} + for i, state_dict in enumerate(state_dicts): + value_dict.update({f"to_k_ip.{i}.weight": state_dict["ip_adapter"][f"{key_id}.to_k_ip.weight"]}) + value_dict.update({f"to_v_ip.{i}.weight": state_dict["ip_adapter"][f"{key_id}.to_v_ip.weight"]}) + + if not low_cpu_mem_usage: + attn_procs[name].load_state_dict(value_dict) + else: + device = next(iter(value_dict.values())).device + dtype = next(iter(value_dict.values())).dtype + load_model_dict_into_meta(attn_procs[name], value_dict, device=device, dtype=dtype) + + key_id += 2 + + return attn_procs + + def _load_ip_adapter_weights(self, state_dicts, low_cpu_mem_usage=False): + attn_procs = self._convert_ip_adapter_attn_to_diffusers(state_dicts, low_cpu_mem_usage=low_cpu_mem_usage) + self.set_attn_processor(attn_procs) + + # convert IP-Adapter Image Projection layers to diffusers + image_projection_layers = [] + for state_dict in state_dicts: + image_projection_layer = self._convert_ip_adapter_image_proj_to_diffusers( + state_dict["image_proj"], low_cpu_mem_usage=low_cpu_mem_usage + ) + image_projection_layers.append(image_projection_layer) + + self.encoder_hid_proj = MultiIPAdapterImageProjection(image_projection_layers) + self.config.encoder_hid_dim_type = "ip_image_proj" + + self.to(dtype=self.dtype, device=self.device) + + def _load_ip_adapter_weights_VPAdapter(self, state_dicts, low_cpu_mem_usage=False): + attn_procs = self._convert_ip_adapter_attn_to_diffusers_VPAdapter(state_dicts, low_cpu_mem_usage=low_cpu_mem_usage) + self.set_attn_processor(attn_procs) + + # convert IP-Adapter Image Projection layers to diffusers + image_projection_layers = [] + for state_dict in state_dicts: + image_projection_layer = self._convert_ip_adapter_image_proj_to_diffusers( + state_dict["image_proj"], low_cpu_mem_usage=low_cpu_mem_usage + ) + image_projection_layers.append(image_projection_layer) + + self.encoder_hid_proj = VPAdapterImageProjection(image_projection_layers) + self.config.encoder_hid_dim_type = "ip_image_proj" + + self.to(dtype=self.dtype, device=self.device) \ No newline at end of file diff --git a/foleycrafter/models/auffusion/resnet.py b/foleycrafter/models/auffusion/resnet.py new file mode 100644 index 0000000000000000000000000000000000000000..6434630129a0ec88eec27b22d3258c591574e39f --- /dev/null +++ b/foleycrafter/models/auffusion/resnet.py @@ -0,0 +1,685 @@ +# Copyright 2023 The HuggingFace Team. All rights reserved. +# `TemporalConvLayer` Copyright 2023 Alibaba DAMO-VILAB, The ModelScope Team and The HuggingFace 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 +# limitations under the License. + +from functools import partial +from typing import Optional, Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from diffusers.utils import USE_PEFT_BACKEND +from diffusers.models.activations import get_activation +from diffusers.models.downsampling import ( # noqa + Downsample1D, + Downsample2D, + FirDownsample2D, + KDownsample2D, + downsample_2d, +) +from diffusers.models.lora import LoRACompatibleConv, LoRACompatibleLinear +from diffusers.models.normalization import AdaGroupNorm +from diffusers.models.upsampling import ( # noqa + FirUpsample2D, + KUpsample2D, + Upsample1D, + Upsample2D, + upfirdn2d_native, + upsample_2d, +) +from foleycrafter.models.auffusion.attention_processor import SpatialNorm + + +class ResnetBlock2D(nn.Module): + r""" + A Resnet block. + + Parameters: + in_channels (`int`): The number of channels in the input. + out_channels (`int`, *optional*, default to be `None`): + The number of output channels for the first conv2d layer. If None, same as `in_channels`. + dropout (`float`, *optional*, defaults to `0.0`): The dropout probability to use. + temb_channels (`int`, *optional*, default to `512`): the number of channels in timestep embedding. + groups (`int`, *optional*, default to `32`): The number of groups to use for the first normalization layer. + groups_out (`int`, *optional*, default to None): + The number of groups to use for the second normalization layer. if set to None, same as `groups`. + eps (`float`, *optional*, defaults to `1e-6`): The epsilon to use for the normalization. + non_linearity (`str`, *optional*, default to `"swish"`): the activation function to use. + time_embedding_norm (`str`, *optional*, default to `"default"` ): Time scale shift config. + By default, apply timestep embedding conditioning with a simple shift mechanism. Choose "scale_shift" or + "ada_group" for a stronger conditioning with scale and shift. + kernel (`torch.FloatTensor`, optional, default to None): FIR filter, see + [`~models.resnet.FirUpsample2D`] and [`~models.resnet.FirDownsample2D`]. + output_scale_factor (`float`, *optional*, default to be `1.0`): the scale factor to use for the output. + use_in_shortcut (`bool`, *optional*, default to `True`): + If `True`, add a 1x1 nn.conv2d layer for skip-connection. + up (`bool`, *optional*, default to `False`): If `True`, add an upsample layer. + down (`bool`, *optional*, default to `False`): If `True`, add a downsample layer. + conv_shortcut_bias (`bool`, *optional*, default to `True`): If `True`, adds a learnable bias to the + `conv_shortcut` output. + conv_2d_out_channels (`int`, *optional*, default to `None`): the number of channels in the output. + If None, same as `out_channels`. + """ + + def __init__( + self, + *, + in_channels: int, + out_channels: Optional[int] = None, + conv_shortcut: bool = False, + dropout: float = 0.0, + temb_channels: int = 512, + groups: int = 32, + groups_out: Optional[int] = None, + pre_norm: bool = True, + eps: float = 1e-6, + non_linearity: str = "swish", + skip_time_act: bool = False, + time_embedding_norm: str = "default", # default, scale_shift, ada_group, spatial + kernel: Optional[torch.FloatTensor] = None, + output_scale_factor: float = 1.0, + use_in_shortcut: Optional[bool] = None, + up: bool = False, + down: bool = False, + conv_shortcut_bias: bool = True, + conv_2d_out_channels: Optional[int] = None, + ): + super().__init__() + self.pre_norm = pre_norm + self.pre_norm = True + self.in_channels = in_channels + out_channels = in_channels if out_channels is None else out_channels + self.out_channels = out_channels + self.use_conv_shortcut = conv_shortcut + self.up = up + self.down = down + self.output_scale_factor = output_scale_factor + self.time_embedding_norm = time_embedding_norm + self.skip_time_act = skip_time_act + + linear_cls = nn.Linear if USE_PEFT_BACKEND else LoRACompatibleLinear + conv_cls = nn.Conv2d if USE_PEFT_BACKEND else LoRACompatibleConv + + if groups_out is None: + groups_out = groups + + if self.time_embedding_norm == "ada_group": + self.norm1 = AdaGroupNorm(temb_channels, in_channels, groups, eps=eps) + elif self.time_embedding_norm == "spatial": + self.norm1 = SpatialNorm(in_channels, temb_channels) + else: + self.norm1 = torch.nn.GroupNorm(num_groups=groups, num_channels=in_channels, eps=eps, affine=True) + + self.conv1 = conv_cls(in_channels, out_channels, kernel_size=3, stride=1, padding=1) + + if temb_channels is not None: + if self.time_embedding_norm == "default": + self.time_emb_proj = linear_cls(temb_channels, out_channels) + elif self.time_embedding_norm == "scale_shift": + self.time_emb_proj = linear_cls(temb_channels, 2 * out_channels) + elif self.time_embedding_norm == "ada_group" or self.time_embedding_norm == "spatial": + self.time_emb_proj = None + else: + raise ValueError(f"unknown time_embedding_norm : {self.time_embedding_norm} ") + else: + self.time_emb_proj = None + + if self.time_embedding_norm == "ada_group": + self.norm2 = AdaGroupNorm(temb_channels, out_channels, groups_out, eps=eps) + elif self.time_embedding_norm == "spatial": + self.norm2 = SpatialNorm(out_channels, temb_channels) + else: + self.norm2 = torch.nn.GroupNorm(num_groups=groups_out, num_channels=out_channels, eps=eps, affine=True) + + self.dropout = torch.nn.Dropout(dropout) + conv_2d_out_channels = conv_2d_out_channels or out_channels + self.conv2 = conv_cls(out_channels, conv_2d_out_channels, kernel_size=3, stride=1, padding=1) + + self.nonlinearity = get_activation(non_linearity) + + self.upsample = self.downsample = None + if self.up: + if kernel == "fir": + fir_kernel = (1, 3, 3, 1) + self.upsample = lambda x: upsample_2d(x, kernel=fir_kernel) + elif kernel == "sde_vp": + self.upsample = partial(F.interpolate, scale_factor=2.0, mode="nearest") + else: + self.upsample = Upsample2D(in_channels, use_conv=False) + elif self.down: + if kernel == "fir": + fir_kernel = (1, 3, 3, 1) + self.downsample = lambda x: downsample_2d(x, kernel=fir_kernel) + elif kernel == "sde_vp": + self.downsample = partial(F.avg_pool2d, kernel_size=2, stride=2) + else: + self.downsample = Downsample2D(in_channels, use_conv=False, padding=1, name="op") + + self.use_in_shortcut = self.in_channels != conv_2d_out_channels if use_in_shortcut is None else use_in_shortcut + + self.conv_shortcut = None + if self.use_in_shortcut: + self.conv_shortcut = conv_cls( + in_channels, + conv_2d_out_channels, + kernel_size=1, + stride=1, + padding=0, + bias=conv_shortcut_bias, + ) + + def forward( + self, + input_tensor: torch.FloatTensor, + temb: torch.FloatTensor, + scale: float = 1.0, + ) -> torch.FloatTensor: + hidden_states = input_tensor + + if self.time_embedding_norm == "ada_group" or self.time_embedding_norm == "spatial": + hidden_states = self.norm1(hidden_states, temb) + else: + hidden_states = self.norm1(hidden_states) + + hidden_states = self.nonlinearity(hidden_states) + + if self.upsample is not None: + # upsample_nearest_nhwc fails with large batch sizes. see https://github.com/huggingface/diffusers/issues/984 + if hidden_states.shape[0] >= 64: + input_tensor = input_tensor.contiguous() + hidden_states = hidden_states.contiguous() + input_tensor = ( + self.upsample(input_tensor, scale=scale) + if isinstance(self.upsample, Upsample2D) + else self.upsample(input_tensor) + ) + hidden_states = ( + self.upsample(hidden_states, scale=scale) + if isinstance(self.upsample, Upsample2D) + else self.upsample(hidden_states) + ) + elif self.downsample is not None: + input_tensor = ( + self.downsample(input_tensor, scale=scale) + if isinstance(self.downsample, Downsample2D) + else self.downsample(input_tensor) + ) + hidden_states = ( + self.downsample(hidden_states, scale=scale) + if isinstance(self.downsample, Downsample2D) + else self.downsample(hidden_states) + ) + + hidden_states = self.conv1(hidden_states, scale) if not USE_PEFT_BACKEND else self.conv1(hidden_states) + + if self.time_emb_proj is not None: + if not self.skip_time_act: + temb = self.nonlinearity(temb) + temb = ( + self.time_emb_proj(temb, scale)[:, :, None, None] + if not USE_PEFT_BACKEND + # NOTE: Maybe we can use different prompt in different time + else self.time_emb_proj(temb)[:, :, None, None] + ) + + if temb is not None and self.time_embedding_norm == "default": + hidden_states = hidden_states + temb + + if self.time_embedding_norm == "ada_group" or self.time_embedding_norm == "spatial": + hidden_states = self.norm2(hidden_states, temb) + else: + hidden_states = self.norm2(hidden_states) + + if temb is not None and self.time_embedding_norm == "scale_shift": + scale, shift = torch.chunk(temb, 2, dim=1) + hidden_states = hidden_states * (1 + scale) + shift + + hidden_states = self.nonlinearity(hidden_states) + + hidden_states = self.dropout(hidden_states) + hidden_states = self.conv2(hidden_states, scale) if not USE_PEFT_BACKEND else self.conv2(hidden_states) + + if self.conv_shortcut is not None: + input_tensor = ( + self.conv_shortcut(input_tensor, scale) if not USE_PEFT_BACKEND else self.conv_shortcut(input_tensor) + ) + + output_tensor = (input_tensor + hidden_states) / self.output_scale_factor + + return output_tensor + + +# unet_rl.py +def rearrange_dims(tensor: torch.Tensor) -> torch.Tensor: + if len(tensor.shape) == 2: + return tensor[:, :, None] + if len(tensor.shape) == 3: + return tensor[:, :, None, :] + elif len(tensor.shape) == 4: + return tensor[:, :, 0, :] + else: + raise ValueError(f"`len(tensor)`: {len(tensor)} has to be 2, 3 or 4.") + + +class Conv1dBlock(nn.Module): + """ + Conv1d --> GroupNorm --> Mish + + Parameters: + inp_channels (`int`): Number of input channels. + out_channels (`int`): Number of output channels. + kernel_size (`int` or `tuple`): Size of the convolving kernel. + n_groups (`int`, default `8`): Number of groups to separate the channels into. + activation (`str`, defaults to `mish`): Name of the activation function. + """ + + def __init__( + self, + inp_channels: int, + out_channels: int, + kernel_size: Union[int, Tuple[int, int]], + n_groups: int = 8, + activation: str = "mish", + ): + super().__init__() + + self.conv1d = nn.Conv1d(inp_channels, out_channels, kernel_size, padding=kernel_size // 2) + self.group_norm = nn.GroupNorm(n_groups, out_channels) + self.mish = get_activation(activation) + + def forward(self, inputs: torch.Tensor) -> torch.Tensor: + intermediate_repr = self.conv1d(inputs) + intermediate_repr = rearrange_dims(intermediate_repr) + intermediate_repr = self.group_norm(intermediate_repr) + intermediate_repr = rearrange_dims(intermediate_repr) + output = self.mish(intermediate_repr) + return output + + +# unet_rl.py +class ResidualTemporalBlock1D(nn.Module): + """ + Residual 1D block with temporal convolutions. + + Parameters: + inp_channels (`int`): Number of input channels. + out_channels (`int`): Number of output channels. + embed_dim (`int`): Embedding dimension. + kernel_size (`int` or `tuple`): Size of the convolving kernel. + activation (`str`, defaults `mish`): It is possible to choose the right activation function. + """ + + def __init__( + self, + inp_channels: int, + out_channels: int, + embed_dim: int, + kernel_size: Union[int, Tuple[int, int]] = 5, + activation: str = "mish", + ): + super().__init__() + self.conv_in = Conv1dBlock(inp_channels, out_channels, kernel_size) + self.conv_out = Conv1dBlock(out_channels, out_channels, kernel_size) + + self.time_emb_act = get_activation(activation) + self.time_emb = nn.Linear(embed_dim, out_channels) + + self.residual_conv = ( + nn.Conv1d(inp_channels, out_channels, 1) if inp_channels != out_channels else nn.Identity() + ) + + def forward(self, inputs: torch.Tensor, t: torch.Tensor) -> torch.Tensor: + """ + Args: + inputs : [ batch_size x inp_channels x horizon ] + t : [ batch_size x embed_dim ] + + returns: + out : [ batch_size x out_channels x horizon ] + """ + t = self.time_emb_act(t) + t = self.time_emb(t) + out = self.conv_in(inputs) + rearrange_dims(t) + out = self.conv_out(out) + return out + self.residual_conv(inputs) + + +class TemporalConvLayer(nn.Module): + """ + Temporal convolutional layer that can be used for video (sequence of images) input Code mostly copied from: + https://github.com/modelscope/modelscope/blob/1509fdb973e5871f37148a4b5e5964cafd43e64d/modelscope/models/multi_modal/video_synthesis/unet_sd.py#L1016 + + Parameters: + in_dim (`int`): Number of input channels. + out_dim (`int`): Number of output channels. + dropout (`float`, *optional*, defaults to `0.0`): The dropout probability to use. + """ + + def __init__( + self, + in_dim: int, + out_dim: Optional[int] = None, + dropout: float = 0.0, + norm_num_groups: int = 32, + ): + super().__init__() + out_dim = out_dim or in_dim + self.in_dim = in_dim + self.out_dim = out_dim + + # conv layers + self.conv1 = nn.Sequential( + nn.GroupNorm(norm_num_groups, in_dim), + nn.SiLU(), + nn.Conv3d(in_dim, out_dim, (3, 1, 1), padding=(1, 0, 0)), + ) + self.conv2 = nn.Sequential( + nn.GroupNorm(norm_num_groups, out_dim), + nn.SiLU(), + nn.Dropout(dropout), + nn.Conv3d(out_dim, in_dim, (3, 1, 1), padding=(1, 0, 0)), + ) + self.conv3 = nn.Sequential( + nn.GroupNorm(norm_num_groups, out_dim), + nn.SiLU(), + nn.Dropout(dropout), + nn.Conv3d(out_dim, in_dim, (3, 1, 1), padding=(1, 0, 0)), + ) + self.conv4 = nn.Sequential( + nn.GroupNorm(norm_num_groups, out_dim), + nn.SiLU(), + nn.Dropout(dropout), + nn.Conv3d(out_dim, in_dim, (3, 1, 1), padding=(1, 0, 0)), + ) + + # zero out the last layer params,so the conv block is identity + nn.init.zeros_(self.conv4[-1].weight) + nn.init.zeros_(self.conv4[-1].bias) + + def forward(self, hidden_states: torch.Tensor, num_frames: int = 1) -> torch.Tensor: + hidden_states = ( + hidden_states[None, :].reshape((-1, num_frames) + hidden_states.shape[1:]).permute(0, 2, 1, 3, 4) + ) + + identity = hidden_states + hidden_states = self.conv1(hidden_states) + hidden_states = self.conv2(hidden_states) + hidden_states = self.conv3(hidden_states) + hidden_states = self.conv4(hidden_states) + + hidden_states = identity + hidden_states + + hidden_states = hidden_states.permute(0, 2, 1, 3, 4).reshape( + (hidden_states.shape[0] * hidden_states.shape[2], -1) + hidden_states.shape[3:] + ) + return hidden_states + + +class TemporalResnetBlock(nn.Module): + r""" + A Resnet block. + + Parameters: + in_channels (`int`): The number of channels in the input. + out_channels (`int`, *optional*, default to be `None`): + The number of output channels for the first conv2d layer. If None, same as `in_channels`. + temb_channels (`int`, *optional*, default to `512`): the number of channels in timestep embedding. + eps (`float`, *optional*, defaults to `1e-6`): The epsilon to use for the normalization. + """ + + def __init__( + self, + in_channels: int, + out_channels: Optional[int] = None, + temb_channels: int = 512, + eps: float = 1e-6, + ): + super().__init__() + self.in_channels = in_channels + out_channels = in_channels if out_channels is None else out_channels + self.out_channels = out_channels + + kernel_size = (3, 1, 1) + padding = [k // 2 for k in kernel_size] + + self.norm1 = torch.nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=eps, affine=True) + self.conv1 = nn.Conv3d( + in_channels, + out_channels, + kernel_size=kernel_size, + stride=1, + padding=padding, + ) + + if temb_channels is not None: + self.time_emb_proj = nn.Linear(temb_channels, out_channels) + else: + self.time_emb_proj = None + + self.norm2 = torch.nn.GroupNorm(num_groups=32, num_channels=out_channels, eps=eps, affine=True) + + self.dropout = torch.nn.Dropout(0.0) + self.conv2 = nn.Conv3d( + out_channels, + out_channels, + kernel_size=kernel_size, + stride=1, + padding=padding, + ) + + self.nonlinearity = get_activation("silu") + + self.use_in_shortcut = self.in_channels != out_channels + + self.conv_shortcut = None + if self.use_in_shortcut: + self.conv_shortcut = nn.Conv3d( + in_channels, + out_channels, + kernel_size=1, + stride=1, + padding=0, + ) + + def forward(self, input_tensor: torch.FloatTensor, temb: torch.FloatTensor) -> torch.FloatTensor: + hidden_states = input_tensor + + hidden_states = self.norm1(hidden_states) + hidden_states = self.nonlinearity(hidden_states) + hidden_states = self.conv1(hidden_states) + + if self.time_emb_proj is not None: + temb = self.nonlinearity(temb) + temb = self.time_emb_proj(temb)[:, :, :, None, None] + temb = temb.permute(0, 2, 1, 3, 4) + hidden_states = hidden_states + temb + + hidden_states = self.norm2(hidden_states) + hidden_states = self.nonlinearity(hidden_states) + hidden_states = self.dropout(hidden_states) + hidden_states = self.conv2(hidden_states) + + if self.conv_shortcut is not None: + input_tensor = self.conv_shortcut(input_tensor) + + output_tensor = input_tensor + hidden_states + + return output_tensor + + +# VideoResBlock +class SpatioTemporalResBlock(nn.Module): + r""" + A SpatioTemporal Resnet block. + + Parameters: + in_channels (`int`): The number of channels in the input. + out_channels (`int`, *optional*, default to be `None`): + The number of output channels for the first conv2d layer. If None, same as `in_channels`. + temb_channels (`int`, *optional*, default to `512`): the number of channels in timestep embedding. + eps (`float`, *optional*, defaults to `1e-6`): The epsilon to use for the spatial resenet. + temporal_eps (`float`, *optional*, defaults to `eps`): The epsilon to use for the temporal resnet. + merge_factor (`float`, *optional*, defaults to `0.5`): The merge factor to use for the temporal mixing. + merge_strategy (`str`, *optional*, defaults to `learned_with_images`): + The merge strategy to use for the temporal mixing. + switch_spatial_to_temporal_mix (`bool`, *optional*, defaults to `False`): + If `True`, switch the spatial and temporal mixing. + """ + + def __init__( + self, + in_channels: int, + out_channels: Optional[int] = None, + temb_channels: int = 512, + eps: float = 1e-6, + temporal_eps: Optional[float] = None, + merge_factor: float = 0.5, + merge_strategy="learned_with_images", + switch_spatial_to_temporal_mix: bool = False, + ): + super().__init__() + + self.spatial_res_block = ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=eps, + ) + + self.temporal_res_block = TemporalResnetBlock( + in_channels=out_channels if out_channels is not None else in_channels, + out_channels=out_channels if out_channels is not None else in_channels, + temb_channels=temb_channels, + eps=temporal_eps if temporal_eps is not None else eps, + ) + + self.time_mixer = AlphaBlender( + alpha=merge_factor, + merge_strategy=merge_strategy, + switch_spatial_to_temporal_mix=switch_spatial_to_temporal_mix, + ) + + def forward( + self, + hidden_states: torch.FloatTensor, + temb: Optional[torch.FloatTensor] = None, + image_only_indicator: Optional[torch.Tensor] = None, + ): + num_frames = image_only_indicator.shape[-1] + hidden_states = self.spatial_res_block(hidden_states, temb) + + batch_frames, channels, height, width = hidden_states.shape + batch_size = batch_frames // num_frames + + hidden_states_mix = ( + hidden_states[None, :].reshape(batch_size, num_frames, channels, height, width).permute(0, 2, 1, 3, 4) + ) + hidden_states = ( + hidden_states[None, :].reshape(batch_size, num_frames, channels, height, width).permute(0, 2, 1, 3, 4) + ) + + if temb is not None: + temb = temb.reshape(batch_size, num_frames, -1) + + hidden_states = self.temporal_res_block(hidden_states, temb) + hidden_states = self.time_mixer( + x_spatial=hidden_states_mix, + x_temporal=hidden_states, + image_only_indicator=image_only_indicator, + ) + + hidden_states = hidden_states.permute(0, 2, 1, 3, 4).reshape(batch_frames, channels, height, width) + return hidden_states + + +class AlphaBlender(nn.Module): + r""" + A module to blend spatial and temporal features. + + Parameters: + alpha (`float`): The initial value of the blending factor. + merge_strategy (`str`, *optional*, defaults to `learned_with_images`): + The merge strategy to use for the temporal mixing. + switch_spatial_to_temporal_mix (`bool`, *optional*, defaults to `False`): + If `True`, switch the spatial and temporal mixing. + """ + + strategies = ["learned", "fixed", "learned_with_images"] + + def __init__( + self, + alpha: float, + merge_strategy: str = "learned_with_images", + switch_spatial_to_temporal_mix: bool = False, + ): + super().__init__() + self.merge_strategy = merge_strategy + self.switch_spatial_to_temporal_mix = switch_spatial_to_temporal_mix # For TemporalVAE + + if merge_strategy not in self.strategies: + raise ValueError(f"merge_strategy needs to be in {self.strategies}") + + if self.merge_strategy == "fixed": + self.register_buffer("mix_factor", torch.Tensor([alpha])) + elif self.merge_strategy == "learned" or self.merge_strategy == "learned_with_images": + self.register_parameter("mix_factor", torch.nn.Parameter(torch.Tensor([alpha]))) + else: + raise ValueError(f"Unknown merge strategy {self.merge_strategy}") + + def get_alpha(self, image_only_indicator: torch.Tensor, ndims: int) -> torch.Tensor: + if self.merge_strategy == "fixed": + alpha = self.mix_factor + + elif self.merge_strategy == "learned": + alpha = torch.sigmoid(self.mix_factor) + + elif self.merge_strategy == "learned_with_images": + if image_only_indicator is None: + raise ValueError("Please provide image_only_indicator to use learned_with_images merge strategy") + + alpha = torch.where( + image_only_indicator.bool(), + torch.ones(1, 1, device=image_only_indicator.device), + torch.sigmoid(self.mix_factor)[..., None], + ) + + # (batch, channel, frames, height, width) + if ndims == 5: + alpha = alpha[:, None, :, None, None] + # (batch*frames, height*width, channels) + elif ndims == 3: + alpha = alpha.reshape(-1)[:, None, None] + else: + raise ValueError(f"Unexpected ndims {ndims}. Dimensions should be 3 or 5") + + else: + raise NotImplementedError + + return alpha + + def forward( + self, + x_spatial: torch.Tensor, + x_temporal: torch.Tensor, + image_only_indicator: Optional[torch.Tensor] = None, + ) -> torch.Tensor: + alpha = self.get_alpha(image_only_indicator, x_spatial.ndim) + alpha = alpha.to(x_spatial.dtype) + + if self.switch_spatial_to_temporal_mix: + alpha = 1.0 - alpha + + x = alpha * x_spatial + (1.0 - alpha) * x_temporal + return x \ No newline at end of file diff --git a/foleycrafter/models/auffusion/transformer_2d.py b/foleycrafter/models/auffusion/transformer_2d.py new file mode 100644 index 0000000000000000000000000000000000000000..0ed523786e81e266eaec914648a779464bc794e5 --- /dev/null +++ b/foleycrafter/models/auffusion/transformer_2d.py @@ -0,0 +1,460 @@ +# Copyright 2023 The HuggingFace 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 +# limitations under the License. +from dataclasses import dataclass +from typing import Any, Dict, Optional + +import torch +import torch.nn.functional as F +from torch import nn + +from diffusers.configuration_utils import ConfigMixin, register_to_config +from diffusers.models.embeddings import ImagePositionalEmbeddings +from diffusers.utils import USE_PEFT_BACKEND, BaseOutput, deprecate, is_torch_version +from diffusers.models.embeddings import PatchEmbed, PixArtAlphaTextProjection +from diffusers.models.lora import LoRACompatibleConv, LoRACompatibleLinear +from diffusers.models.modeling_utils import ModelMixin +from diffusers.models.normalization import AdaLayerNormSingle + +from foleycrafter.models.auffusion.attention import BasicTransformerBlock + +@dataclass +class Transformer2DModelOutput(BaseOutput): + """ + The output of [`Transformer2DModel`]. + + Args: + sample (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)` or `(batch size, num_vector_embeds - 1, num_latent_pixels)` if [`Transformer2DModel`] is discrete): + The hidden states output conditioned on the `encoder_hidden_states` input. If discrete, returns probability + distributions for the unnoised latent pixels. + """ + + sample: torch.FloatTensor + +class Transformer2DModel(ModelMixin, ConfigMixin): + """ + A 2D Transformer model for image-like data. + + Parameters: + num_attention_heads (`int`, *optional*, defaults to 16): The number of heads to use for multi-head attention. + attention_head_dim (`int`, *optional*, defaults to 88): The number of channels in each head. + in_channels (`int`, *optional*): + The number of channels in the input and output (specify if the input is **continuous**). + num_layers (`int`, *optional*, defaults to 1): The number of layers of Transformer blocks to use. + dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use. + cross_attention_dim (`int`, *optional*): The number of `encoder_hidden_states` dimensions to use. + sample_size (`int`, *optional*): The width of the latent images (specify if the input is **discrete**). + This is fixed during training since it is used to learn a number of position embeddings. + num_vector_embeds (`int`, *optional*): + The number of classes of the vector embeddings of the latent pixels (specify if the input is **discrete**). + Includes the class for the masked latent pixel. + activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to use in feed-forward. + num_embeds_ada_norm ( `int`, *optional*): + The number of diffusion steps used during training. Pass if at least one of the norm_layers is + `AdaLayerNorm`. This is fixed during training since it is used to learn a number of embeddings that are + added to the hidden states. + + During inference, you can denoise for up to but not more steps than `num_embeds_ada_norm`. + attention_bias (`bool`, *optional*): + Configure if the `TransformerBlocks` attention should contain a bias parameter. + """ + + _supports_gradient_checkpointing = True + + @register_to_config + def __init__( + self, + num_attention_heads: int = 16, + attention_head_dim: int = 88, + in_channels: Optional[int] = None, + out_channels: Optional[int] = None, + num_layers: int = 1, + dropout: float = 0.0, + norm_num_groups: int = 32, + cross_attention_dim: Optional[int] = None, + attention_bias: bool = False, + sample_size: Optional[int] = None, + num_vector_embeds: Optional[int] = None, + patch_size: Optional[int] = None, + activation_fn: str = "geglu", + num_embeds_ada_norm: Optional[int] = None, + use_linear_projection: bool = False, + only_cross_attention: bool = False, + double_self_attention: bool = False, + upcast_attention: bool = False, + norm_type: str = "layer_norm", + norm_elementwise_affine: bool = True, + norm_eps: float = 1e-5, + attention_type: str = "default", + caption_channels: int = None, + ): + super().__init__() + self.use_linear_projection = use_linear_projection + self.num_attention_heads = num_attention_heads + self.attention_head_dim = attention_head_dim + inner_dim = num_attention_heads * attention_head_dim + + conv_cls = nn.Conv2d if USE_PEFT_BACKEND else LoRACompatibleConv + linear_cls = nn.Linear if USE_PEFT_BACKEND else LoRACompatibleLinear + + # 1. Transformer2DModel can process both standard continuous images of shape `(batch_size, num_channels, width, height)` as well as quantized image embeddings of shape `(batch_size, num_image_vectors)` + # Define whether input is continuous or discrete depending on configuration + self.is_input_continuous = (in_channels is not None) and (patch_size is None) + self.is_input_vectorized = num_vector_embeds is not None + self.is_input_patches = in_channels is not None and patch_size is not None + + if norm_type == "layer_norm" and num_embeds_ada_norm is not None: + deprecation_message = ( + f"The configuration file of this model: {self.__class__} is outdated. `norm_type` is either not set or" + " incorrectly set to `'layer_norm'`.Make sure to set `norm_type` to `'ada_norm'` in the config." + " Please make sure to update the config accordingly as leaving `norm_type` might led to incorrect" + " results in future versions. If you have downloaded this checkpoint from the Hugging Face Hub, it" + " would be very nice if you could open a Pull request for the `transformer/config.json` file" + ) + deprecate("norm_type!=num_embeds_ada_norm", "1.0.0", deprecation_message, standard_warn=False) + norm_type = "ada_norm" + + if self.is_input_continuous and self.is_input_vectorized: + raise ValueError( + f"Cannot define both `in_channels`: {in_channels} and `num_vector_embeds`: {num_vector_embeds}. Make" + " sure that either `in_channels` or `num_vector_embeds` is None." + ) + elif self.is_input_vectorized and self.is_input_patches: + raise ValueError( + f"Cannot define both `num_vector_embeds`: {num_vector_embeds} and `patch_size`: {patch_size}. Make" + " sure that either `num_vector_embeds` or `num_patches` is None." + ) + elif not self.is_input_continuous and not self.is_input_vectorized and not self.is_input_patches: + raise ValueError( + f"Has to define `in_channels`: {in_channels}, `num_vector_embeds`: {num_vector_embeds}, or patch_size:" + f" {patch_size}. Make sure that `in_channels`, `num_vector_embeds` or `num_patches` is not None." + ) + + # 2. Define input layers + if self.is_input_continuous: + self.in_channels = in_channels + + self.norm = torch.nn.GroupNorm(num_groups=norm_num_groups, num_channels=in_channels, eps=1e-6, affine=True) + if use_linear_projection: + self.proj_in = linear_cls(in_channels, inner_dim) + else: + self.proj_in = conv_cls(in_channels, inner_dim, kernel_size=1, stride=1, padding=0) + elif self.is_input_vectorized: + assert sample_size is not None, "Transformer2DModel over discrete input must provide sample_size" + assert num_vector_embeds is not None, "Transformer2DModel over discrete input must provide num_embed" + + self.height = sample_size + self.width = sample_size + self.num_vector_embeds = num_vector_embeds + self.num_latent_pixels = self.height * self.width + + self.latent_image_embedding = ImagePositionalEmbeddings( + num_embed=num_vector_embeds, embed_dim=inner_dim, height=self.height, width=self.width + ) + elif self.is_input_patches: + assert sample_size is not None, "Transformer2DModel over patched input must provide sample_size" + + self.height = sample_size + self.width = sample_size + + self.patch_size = patch_size + interpolation_scale = self.config.sample_size // 64 # => 64 (= 512 pixart) has interpolation scale 1 + interpolation_scale = max(interpolation_scale, 1) + self.pos_embed = PatchEmbed( + height=sample_size, + width=sample_size, + patch_size=patch_size, + in_channels=in_channels, + embed_dim=inner_dim, + interpolation_scale=interpolation_scale, + ) + + # 3. Define transformers blocks + self.transformer_blocks = nn.ModuleList( + [ + # NOTE: remember to change + BasicTransformerBlock( + inner_dim, + num_attention_heads, + attention_head_dim, + dropout=dropout, + cross_attention_dim=cross_attention_dim, + activation_fn=activation_fn, + num_embeds_ada_norm=num_embeds_ada_norm, + attention_bias=attention_bias, + only_cross_attention=only_cross_attention, + double_self_attention=double_self_attention, + upcast_attention=upcast_attention, + norm_type=norm_type, + norm_elementwise_affine=norm_elementwise_affine, + norm_eps=norm_eps, + attention_type=attention_type, + ) + for d in range(num_layers) + ] + ) + + # 4. Define output layers + self.out_channels = in_channels if out_channels is None else out_channels + if self.is_input_continuous: + # TODO: should use out_channels for continuous projections + if use_linear_projection: + self.proj_out = linear_cls(inner_dim, in_channels) + else: + self.proj_out = conv_cls(inner_dim, in_channels, kernel_size=1, stride=1, padding=0) + elif self.is_input_vectorized: + self.norm_out = nn.LayerNorm(inner_dim) + self.out = nn.Linear(inner_dim, self.num_vector_embeds - 1) + elif self.is_input_patches and norm_type != "ada_norm_single": + self.norm_out = nn.LayerNorm(inner_dim, elementwise_affine=False, eps=1e-6) + self.proj_out_1 = nn.Linear(inner_dim, 2 * inner_dim) + self.proj_out_2 = nn.Linear(inner_dim, patch_size * patch_size * self.out_channels) + elif self.is_input_patches and norm_type == "ada_norm_single": + self.norm_out = nn.LayerNorm(inner_dim, elementwise_affine=False, eps=1e-6) + self.scale_shift_table = nn.Parameter(torch.randn(2, inner_dim) / inner_dim**0.5) + self.proj_out = nn.Linear(inner_dim, patch_size * patch_size * self.out_channels) + + # 5. PixArt-Alpha blocks. + self.adaln_single = None + self.use_additional_conditions = False + if norm_type == "ada_norm_single": + self.use_additional_conditions = self.config.sample_size == 128 + # TODO(Sayak, PVP) clean this, for now we use sample size to determine whether to use + # additional conditions until we find better name + self.adaln_single = AdaLayerNormSingle(inner_dim, use_additional_conditions=self.use_additional_conditions) + + self.caption_projection = None + if caption_channels is not None: + self.caption_projection = PixArtAlphaTextProjection(in_features=caption_channels, hidden_size=inner_dim) + + self.gradient_checkpointing = False + + def _set_gradient_checkpointing(self, module, value=False): + if hasattr(module, "gradient_checkpointing"): + module.gradient_checkpointing = value + + def forward( + self, + hidden_states: torch.Tensor, + encoder_hidden_states: Optional[torch.Tensor] = None, + timestep: Optional[torch.LongTensor] = None, + added_cond_kwargs: Dict[str, torch.Tensor] = None, + class_labels: Optional[torch.LongTensor] = None, + cross_attention_kwargs: Dict[str, Any] = None, + attention_mask: Optional[torch.Tensor] = None, + encoder_attention_mask: Optional[torch.Tensor] = None, + return_dict: bool = True, + ): + """ + The [`Transformer2DModel`] forward method. + + Args: + hidden_states (`torch.LongTensor` of shape `(batch size, num latent pixels)` if discrete, `torch.FloatTensor` of shape `(batch size, channel, height, width)` if continuous): + Input `hidden_states`. + encoder_hidden_states ( `torch.FloatTensor` of shape `(batch size, sequence len, embed dims)`, *optional*): + Conditional embeddings for cross attention layer. If not given, cross-attention defaults to + self-attention. + timestep ( `torch.LongTensor`, *optional*): + Used to indicate denoising step. Optional timestep to be applied as an embedding in `AdaLayerNorm`. + class_labels ( `torch.LongTensor` of shape `(batch size, num classes)`, *optional*): + Used to indicate class labels conditioning. Optional class labels to be applied as an embedding in + `AdaLayerZeroNorm`. + cross_attention_kwargs ( `Dict[str, Any]`, *optional*): + A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under + `self.processor` in + [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + attention_mask ( `torch.Tensor`, *optional*): + An attention mask of shape `(batch, key_tokens)` is applied to `encoder_hidden_states`. If `1` the mask + is kept, otherwise if `0` it is discarded. Mask will be converted into a bias, which adds large + negative values to the attention scores corresponding to "discard" tokens. + encoder_attention_mask ( `torch.Tensor`, *optional*): + Cross-attention mask applied to `encoder_hidden_states`. Two formats supported: + + * Mask `(batch, sequence_length)` True = keep, False = discard. + * Bias `(batch, 1, sequence_length)` 0 = keep, -10000 = discard. + + If `ndim == 2`: will be interpreted as a mask, then converted into a bias consistent with the format + above. This bias will be added to the cross-attention scores. + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~models.unet_2d_condition.UNet2DConditionOutput`] instead of a plain + tuple. + + Returns: + If `return_dict` is True, an [`~models.transformer_2d.Transformer2DModelOutput`] is returned, otherwise a + `tuple` where the first element is the sample tensor. + """ + # ensure attention_mask is a bias, and give it a singleton query_tokens dimension. + # we may have done this conversion already, e.g. if we came here via UNet2DConditionModel#forward. + # we can tell by counting dims; if ndim == 2: it's a mask rather than a bias. + # expects mask of shape: + # [batch, key_tokens] + # adds singleton query_tokens dimension: + # [batch, 1, key_tokens] + # this helps to broadcast it as a bias over attention scores, which will be in one of the following shapes: + # [batch, heads, query_tokens, key_tokens] (e.g. torch sdp attn) + # [batch * heads, query_tokens, key_tokens] (e.g. xformers or classic attn) + if attention_mask is not None and attention_mask.ndim == 2: + # assume that mask is expressed as: + # (1 = keep, 0 = discard) + # convert mask into a bias that can be added to attention scores: + # (keep = +0, discard = -10000.0) + attention_mask = (1 - attention_mask.to(hidden_states.dtype)) * -10000.0 + attention_mask = attention_mask.unsqueeze(1) + + # convert encoder_attention_mask to a bias the same way we do for attention_mask + if encoder_attention_mask is not None and encoder_attention_mask.ndim == 2: + encoder_attention_mask = (1 - encoder_attention_mask.to(hidden_states.dtype)) * -10000.0 + encoder_attention_mask = encoder_attention_mask.unsqueeze(1) + + # Retrieve lora scale. + lora_scale = cross_attention_kwargs.get("scale", 1.0) if cross_attention_kwargs is not None else 1.0 + + # 1. Input + if self.is_input_continuous: + batch, _, height, width = hidden_states.shape + inner_dim = hidden_states.shape[1] + residual = hidden_states + + hidden_states = self.norm(hidden_states) + if not self.use_linear_projection: + hidden_states = ( + self.proj_in(hidden_states, scale=lora_scale) + if not USE_PEFT_BACKEND + else self.proj_in(hidden_states) + ) + inner_dim = hidden_states.shape[1] + hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(batch, height * width, inner_dim) + else: + inner_dim = hidden_states.shape[1] + hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(batch, height * width, inner_dim) + hidden_states = ( + self.proj_in(hidden_states, scale=lora_scale) + if not USE_PEFT_BACKEND + else self.proj_in(hidden_states) + ) + + elif self.is_input_vectorized: + hidden_states = self.latent_image_embedding(hidden_states) + elif self.is_input_patches: + height, width = hidden_states.shape[-2] // self.patch_size, hidden_states.shape[-1] // self.patch_size + self.height, self.width = height, width + hidden_states = self.pos_embed(hidden_states) + + if self.adaln_single is not None: + if self.use_additional_conditions and added_cond_kwargs is None: + raise ValueError( + "`added_cond_kwargs` cannot be None when using additional conditions for `adaln_single`." + ) + batch_size = hidden_states.shape[0] + timestep, embedded_timestep = self.adaln_single( + timestep, added_cond_kwargs, batch_size=batch_size, hidden_dtype=hidden_states.dtype + ) + + if self.caption_projection is not None: + batch_size = hidden_states.shape[0] + encoder_hidden_states = self.caption_projection(encoder_hidden_states) + encoder_hidden_states = encoder_hidden_states.view(batch_size, -1, hidden_states.shape[-1]) + # 2. Blocks + for block in self.transformer_blocks: + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(block), + hidden_states, + attention_mask, + encoder_hidden_states, + encoder_attention_mask, + timestep, + cross_attention_kwargs, + class_labels, + **ckpt_kwargs, + ) + else: + hidden_states = block( + hidden_states, + attention_mask=attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=encoder_attention_mask, + timestep=timestep, + cross_attention_kwargs=cross_attention_kwargs, + class_labels=class_labels, + ) + + # 3. Output + if self.is_input_continuous: + if not self.use_linear_projection: + hidden_states = hidden_states.reshape(batch, height, width, inner_dim).permute(0, 3, 1, 2).contiguous() + hidden_states = ( + self.proj_out(hidden_states, scale=lora_scale) + if not USE_PEFT_BACKEND + else self.proj_out(hidden_states) + ) + else: + hidden_states = ( + self.proj_out(hidden_states, scale=lora_scale) + if not USE_PEFT_BACKEND + else self.proj_out(hidden_states) + ) + hidden_states = hidden_states.reshape(batch, height, width, inner_dim).permute(0, 3, 1, 2).contiguous() + + output = hidden_states + residual + elif self.is_input_vectorized: + hidden_states = self.norm_out(hidden_states) + logits = self.out(hidden_states) + # (batch, self.num_vector_embeds - 1, self.num_latent_pixels) + logits = logits.permute(0, 2, 1) + + # log(p(x_0)) + output = F.log_softmax(logits.double(), dim=1).float() + + if self.is_input_patches: + if self.config.norm_type != "ada_norm_single": + conditioning = self.transformer_blocks[0].norm1.emb( + timestep, class_labels, hidden_dtype=hidden_states.dtype + ) + shift, scale = self.proj_out_1(F.silu(conditioning)).chunk(2, dim=1) + hidden_states = self.norm_out(hidden_states) * (1 + scale[:, None]) + shift[:, None] + hidden_states = self.proj_out_2(hidden_states) + elif self.config.norm_type == "ada_norm_single": + shift, scale = (self.scale_shift_table[None] + embedded_timestep[:, None]).chunk(2, dim=1) + hidden_states = self.norm_out(hidden_states) + # Modulation + hidden_states = hidden_states * (1 + scale) + shift + hidden_states = self.proj_out(hidden_states) + hidden_states = hidden_states.squeeze(1) + + # unpatchify + if self.adaln_single is None: + height = width = int(hidden_states.shape[1] ** 0.5) + hidden_states = hidden_states.reshape( + shape=(-1, height, width, self.patch_size, self.patch_size, self.out_channels) + ) + hidden_states = torch.einsum("nhwpqc->nchpwq", hidden_states) + output = hidden_states.reshape( + shape=(-1, self.out_channels, height * self.patch_size, width * self.patch_size) + ) + + if not return_dict: + return (output,) + + return Transformer2DModelOutput(sample=output) \ No newline at end of file diff --git a/foleycrafter/models/auffusion/unet_2d_blocks.py b/foleycrafter/models/auffusion/unet_2d_blocks.py new file mode 100644 index 0000000000000000000000000000000000000000..1c186bd2113a36c2502f5059b08d16b67eb74817 --- /dev/null +++ b/foleycrafter/models/auffusion/unet_2d_blocks.py @@ -0,0 +1,3498 @@ +# Copyright 2023 The HuggingFace 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 +# limitations under the License. +from typing import Any, Dict, Optional, Tuple, Union + +import numpy as np +import torch +import torch.nn.functional as F +from torch import nn + +from diffusers.utils import is_torch_version, logging +from diffusers.utils.torch_utils import apply_freeu +from diffusers.models.activations import get_activation +from diffusers.models.normalization import AdaGroupNorm + +from foleycrafter.models.auffusion.resnet import \ + Downsample2D, FirDownsample2D, FirUpsample2D, \ + KDownsample2D, KUpsample2D, ResnetBlock2D, Upsample2D +from foleycrafter.models.auffusion.transformer_2d import \ + Transformer2DModel +from foleycrafter.models.auffusion.dual_transformer_2d import \ + DualTransformer2DModel +from foleycrafter.models.auffusion.attention_processor import \ + Attention, AttnAddedKVProcessor, AttnAddedKVProcessor2_0 + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + +def get_down_block( + down_block_type: str, + num_layers: int, + in_channels: int, + out_channels: int, + temb_channels: int, + add_downsample: bool, + resnet_eps: float, + resnet_act_fn: str, + transformer_layers_per_block: int = 1, + num_attention_heads: Optional[int] = None, + resnet_groups: Optional[int] = None, + cross_attention_dim: Optional[int] = None, + downsample_padding: Optional[int] = None, + dual_cross_attention: bool = False, + use_linear_projection: bool = False, + only_cross_attention: bool = False, + upcast_attention: bool = False, + resnet_time_scale_shift: str = "default", + attention_type: str = "default", + resnet_skip_time_act: bool = False, + resnet_out_scale_factor: float = 1.0, + cross_attention_norm: Optional[str] = None, + attention_head_dim: Optional[int] = None, + downsample_type: Optional[str] = None, + dropout: float = 0.0, +): + # If attn head dim is not defined, we default it to the number of heads + if attention_head_dim is None: + logger.warn( + f"It is recommended to provide `attention_head_dim` when calling `get_down_block`. Defaulting `attention_head_dim` to {num_attention_heads}." + ) + attention_head_dim = num_attention_heads + + down_block_type = down_block_type[7:] if down_block_type.startswith("UNetRes") else down_block_type + if down_block_type == "DownBlock2D": + return DownBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + dropout=dropout, + add_downsample=add_downsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + downsample_padding=downsample_padding, + resnet_time_scale_shift=resnet_time_scale_shift, + ) + elif down_block_type == "ResnetDownsampleBlock2D": + return ResnetDownsampleBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + dropout=dropout, + add_downsample=add_downsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + resnet_time_scale_shift=resnet_time_scale_shift, + skip_time_act=resnet_skip_time_act, + output_scale_factor=resnet_out_scale_factor, + ) + elif down_block_type == "AttnDownBlock2D": + if add_downsample is False: + downsample_type = None + else: + downsample_type = downsample_type or "conv" # default to 'conv' + return AttnDownBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + dropout=dropout, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + downsample_padding=downsample_padding, + attention_head_dim=attention_head_dim, + resnet_time_scale_shift=resnet_time_scale_shift, + downsample_type=downsample_type, + ) + elif down_block_type == "CrossAttnDownBlock2D": + if cross_attention_dim is None: + raise ValueError("cross_attention_dim must be specified for CrossAttnDownBlock2D") + return CrossAttnDownBlock2D( + num_layers=num_layers, + transformer_layers_per_block=transformer_layers_per_block, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + dropout=dropout, + add_downsample=add_downsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + downsample_padding=downsample_padding, + cross_attention_dim=cross_attention_dim, + num_attention_heads=num_attention_heads, + dual_cross_attention=dual_cross_attention, + use_linear_projection=use_linear_projection, + only_cross_attention=only_cross_attention, + upcast_attention=upcast_attention, + resnet_time_scale_shift=resnet_time_scale_shift, + attention_type=attention_type, + ) + elif down_block_type == "SimpleCrossAttnDownBlock2D": + if cross_attention_dim is None: + raise ValueError("cross_attention_dim must be specified for SimpleCrossAttnDownBlock2D") + return SimpleCrossAttnDownBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + dropout=dropout, + add_downsample=add_downsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + cross_attention_dim=cross_attention_dim, + attention_head_dim=attention_head_dim, + resnet_time_scale_shift=resnet_time_scale_shift, + skip_time_act=resnet_skip_time_act, + output_scale_factor=resnet_out_scale_factor, + only_cross_attention=only_cross_attention, + cross_attention_norm=cross_attention_norm, + ) + elif down_block_type == "SkipDownBlock2D": + return SkipDownBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + dropout=dropout, + add_downsample=add_downsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + downsample_padding=downsample_padding, + resnet_time_scale_shift=resnet_time_scale_shift, + ) + elif down_block_type == "AttnSkipDownBlock2D": + return AttnSkipDownBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + dropout=dropout, + add_downsample=add_downsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + attention_head_dim=attention_head_dim, + resnet_time_scale_shift=resnet_time_scale_shift, + ) + elif down_block_type == "DownEncoderBlock2D": + return DownEncoderBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + dropout=dropout, + add_downsample=add_downsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + downsample_padding=downsample_padding, + resnet_time_scale_shift=resnet_time_scale_shift, + ) + elif down_block_type == "AttnDownEncoderBlock2D": + return AttnDownEncoderBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + dropout=dropout, + add_downsample=add_downsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + downsample_padding=downsample_padding, + attention_head_dim=attention_head_dim, + resnet_time_scale_shift=resnet_time_scale_shift, + ) + elif down_block_type == "KDownBlock2D": + return KDownBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + dropout=dropout, + add_downsample=add_downsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + ) + elif down_block_type == "KCrossAttnDownBlock2D": + return KCrossAttnDownBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + dropout=dropout, + add_downsample=add_downsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + cross_attention_dim=cross_attention_dim, + attention_head_dim=attention_head_dim, + add_self_attention=True if not add_downsample else False, + ) + raise ValueError(f"{down_block_type} does not exist.") + + +def get_up_block( + up_block_type: str, + num_layers: int, + in_channels: int, + out_channels: int, + prev_output_channel: int, + temb_channels: int, + add_upsample: bool, + resnet_eps: float, + resnet_act_fn: str, + resolution_idx: Optional[int] = None, + transformer_layers_per_block: int = 1, + num_attention_heads: Optional[int] = None, + resnet_groups: Optional[int] = None, + cross_attention_dim: Optional[int] = None, + dual_cross_attention: bool = False, + use_linear_projection: bool = False, + only_cross_attention: bool = False, + upcast_attention: bool = False, + resnet_time_scale_shift: str = "default", + attention_type: str = "default", + resnet_skip_time_act: bool = False, + resnet_out_scale_factor: float = 1.0, + cross_attention_norm: Optional[str] = None, + attention_head_dim: Optional[int] = None, + upsample_type: Optional[str] = None, + dropout: float = 0.0, +) -> nn.Module: + # If attn head dim is not defined, we default it to the number of heads + if attention_head_dim is None: + logger.warn( + f"It is recommended to provide `attention_head_dim` when calling `get_up_block`. Defaulting `attention_head_dim` to {num_attention_heads}." + ) + attention_head_dim = num_attention_heads + + up_block_type = up_block_type[7:] if up_block_type.startswith("UNetRes") else up_block_type + if up_block_type == "UpBlock2D": + return UpBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + prev_output_channel=prev_output_channel, + temb_channels=temb_channels, + resolution_idx=resolution_idx, + dropout=dropout, + add_upsample=add_upsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + resnet_time_scale_shift=resnet_time_scale_shift, + ) + elif up_block_type == "ResnetUpsampleBlock2D": + return ResnetUpsampleBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + prev_output_channel=prev_output_channel, + temb_channels=temb_channels, + resolution_idx=resolution_idx, + dropout=dropout, + add_upsample=add_upsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + resnet_time_scale_shift=resnet_time_scale_shift, + skip_time_act=resnet_skip_time_act, + output_scale_factor=resnet_out_scale_factor, + ) + elif up_block_type == "CrossAttnUpBlock2D": + if cross_attention_dim is None: + raise ValueError("cross_attention_dim must be specified for CrossAttnUpBlock2D") + return CrossAttnUpBlock2D( + num_layers=num_layers, + transformer_layers_per_block=transformer_layers_per_block, + in_channels=in_channels, + out_channels=out_channels, + prev_output_channel=prev_output_channel, + temb_channels=temb_channels, + resolution_idx=resolution_idx, + dropout=dropout, + add_upsample=add_upsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + cross_attention_dim=cross_attention_dim, + num_attention_heads=num_attention_heads, + dual_cross_attention=dual_cross_attention, + use_linear_projection=use_linear_projection, + only_cross_attention=only_cross_attention, + upcast_attention=upcast_attention, + resnet_time_scale_shift=resnet_time_scale_shift, + attention_type=attention_type, + ) + elif up_block_type == "SimpleCrossAttnUpBlock2D": + if cross_attention_dim is None: + raise ValueError("cross_attention_dim must be specified for SimpleCrossAttnUpBlock2D") + return SimpleCrossAttnUpBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + prev_output_channel=prev_output_channel, + temb_channels=temb_channels, + resolution_idx=resolution_idx, + dropout=dropout, + add_upsample=add_upsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + cross_attention_dim=cross_attention_dim, + attention_head_dim=attention_head_dim, + resnet_time_scale_shift=resnet_time_scale_shift, + skip_time_act=resnet_skip_time_act, + output_scale_factor=resnet_out_scale_factor, + only_cross_attention=only_cross_attention, + cross_attention_norm=cross_attention_norm, + ) + elif up_block_type == "AttnUpBlock2D": + if add_upsample is False: + upsample_type = None + else: + upsample_type = upsample_type or "conv" # default to 'conv' + + return AttnUpBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + prev_output_channel=prev_output_channel, + temb_channels=temb_channels, + resolution_idx=resolution_idx, + dropout=dropout, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + attention_head_dim=attention_head_dim, + resnet_time_scale_shift=resnet_time_scale_shift, + upsample_type=upsample_type, + ) + elif up_block_type == "SkipUpBlock2D": + return SkipUpBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + prev_output_channel=prev_output_channel, + temb_channels=temb_channels, + resolution_idx=resolution_idx, + dropout=dropout, + add_upsample=add_upsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_time_scale_shift=resnet_time_scale_shift, + ) + elif up_block_type == "AttnSkipUpBlock2D": + return AttnSkipUpBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + prev_output_channel=prev_output_channel, + temb_channels=temb_channels, + resolution_idx=resolution_idx, + dropout=dropout, + add_upsample=add_upsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + attention_head_dim=attention_head_dim, + resnet_time_scale_shift=resnet_time_scale_shift, + ) + elif up_block_type == "UpDecoderBlock2D": + return UpDecoderBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + resolution_idx=resolution_idx, + dropout=dropout, + add_upsample=add_upsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + resnet_time_scale_shift=resnet_time_scale_shift, + temb_channels=temb_channels, + ) + elif up_block_type == "AttnUpDecoderBlock2D": + return AttnUpDecoderBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + resolution_idx=resolution_idx, + dropout=dropout, + add_upsample=add_upsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + resnet_groups=resnet_groups, + attention_head_dim=attention_head_dim, + resnet_time_scale_shift=resnet_time_scale_shift, + temb_channels=temb_channels, + ) + elif up_block_type == "KUpBlock2D": + return KUpBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + resolution_idx=resolution_idx, + dropout=dropout, + add_upsample=add_upsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + ) + elif up_block_type == "KCrossAttnUpBlock2D": + return KCrossAttnUpBlock2D( + num_layers=num_layers, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + resolution_idx=resolution_idx, + dropout=dropout, + add_upsample=add_upsample, + resnet_eps=resnet_eps, + resnet_act_fn=resnet_act_fn, + cross_attention_dim=cross_attention_dim, + attention_head_dim=attention_head_dim, + ) + + raise ValueError(f"{up_block_type} does not exist.") + + +class AutoencoderTinyBlock(nn.Module): + """ + Tiny Autoencoder block used in [`AutoencoderTiny`]. It is a mini residual module consisting of plain conv + ReLU + blocks. + + Args: + in_channels (`int`): The number of input channels. + out_channels (`int`): The number of output channels. + act_fn (`str`): + ` The activation function to use. Supported values are `"swish"`, `"mish"`, `"gelu"`, and `"relu"`. + + Returns: + `torch.FloatTensor`: A tensor with the same shape as the input tensor, but with the number of channels equal to + `out_channels`. + """ + + def __init__(self, in_channels: int, out_channels: int, act_fn: str): + super().__init__() + act_fn = get_activation(act_fn) + self.conv = nn.Sequential( + nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1), + act_fn, + nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1), + act_fn, + nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1), + ) + self.skip = ( + nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False) + if in_channels != out_channels + else nn.Identity() + ) + self.fuse = nn.ReLU() + + def forward(self, x: torch.FloatTensor) -> torch.FloatTensor: + return self.fuse(self.conv(x) + self.skip(x)) + + +class UNetMidBlock2D(nn.Module): + """ + A 2D UNet mid-block [`UNetMidBlock2D`] with multiple residual blocks and optional attention blocks. + + Args: + in_channels (`int`): The number of input channels. + temb_channels (`int`): The number of temporal embedding channels. + dropout (`float`, *optional*, defaults to 0.0): The dropout rate. + num_layers (`int`, *optional*, defaults to 1): The number of residual blocks. + resnet_eps (`float`, *optional*, 1e-6 ): The epsilon value for the resnet blocks. + resnet_time_scale_shift (`str`, *optional*, defaults to `default`): + The type of normalization to apply to the time embeddings. This can help to improve the performance of the + model on tasks with long-range temporal dependencies. + resnet_act_fn (`str`, *optional*, defaults to `swish`): The activation function for the resnet blocks. + resnet_groups (`int`, *optional*, defaults to 32): + The number of groups to use in the group normalization layers of the resnet blocks. + attn_groups (`Optional[int]`, *optional*, defaults to None): The number of groups for the attention blocks. + resnet_pre_norm (`bool`, *optional*, defaults to `True`): + Whether to use pre-normalization for the resnet blocks. + add_attention (`bool`, *optional*, defaults to `True`): Whether to add attention blocks. + attention_head_dim (`int`, *optional*, defaults to 1): + Dimension of a single attention head. The number of attention heads is determined based on this value and + the number of input channels. + output_scale_factor (`float`, *optional*, defaults to 1.0): The output scale factor. + + Returns: + `torch.FloatTensor`: The output of the last residual block, which is a tensor of shape `(batch_size, + in_channels, height, width)`. + + """ + + def __init__( + self, + in_channels: int, + temb_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", # default, spatial + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + attn_groups: Optional[int] = None, + resnet_pre_norm: bool = True, + add_attention: bool = True, + attention_head_dim: int = 1, + output_scale_factor: float = 1.0, + ): + super().__init__() + resnet_groups = resnet_groups if resnet_groups is not None else min(in_channels // 4, 32) + self.add_attention = add_attention + + if attn_groups is None: + attn_groups = resnet_groups if resnet_time_scale_shift == "default" else None + + # there is always at least one resnet + resnets = [ + ResnetBlock2D( + in_channels=in_channels, + out_channels=in_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ] + attentions = [] + + if attention_head_dim is None: + logger.warn( + f"It is not recommend to pass `attention_head_dim=None`. Defaulting `attention_head_dim` to `in_channels`: {in_channels}." + ) + attention_head_dim = in_channels + + for _ in range(num_layers): + if self.add_attention: + attentions.append( + Attention( + in_channels, + heads=in_channels // attention_head_dim, + dim_head=attention_head_dim, + rescale_output_factor=output_scale_factor, + eps=resnet_eps, + norm_num_groups=attn_groups, + spatial_norm_dim=temb_channels if resnet_time_scale_shift == "spatial" else None, + residual_connection=True, + bias=True, + upcast_softmax=True, + _from_deprecated_attn_block=True, + ) + ) + else: + attentions.append(None) + + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=in_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + def forward(self, hidden_states: torch.FloatTensor, temb: Optional[torch.FloatTensor] = None) -> torch.FloatTensor: + hidden_states = self.resnets[0](hidden_states, temb) + for attn, resnet in zip(self.attentions, self.resnets[1:]): + if attn is not None: + hidden_states = attn(hidden_states, temb=temb) + hidden_states = resnet(hidden_states, temb) + + return hidden_states + + +class UNetMidBlock2DCrossAttn(nn.Module): + def __init__( + self, + in_channels: int, + temb_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + transformer_layers_per_block: Union[int, Tuple[int]] = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + num_attention_heads: int = 1, + output_scale_factor: float = 1.0, + cross_attention_dim: int = 1280, + dual_cross_attention: bool = False, + use_linear_projection: bool = False, + upcast_attention: bool = False, + attention_type: str = "default", + ): + super().__init__() + + self.has_cross_attention = True + self.num_attention_heads = num_attention_heads + resnet_groups = resnet_groups if resnet_groups is not None else min(in_channels // 4, 32) + + # support for variable transformer layers per block + if isinstance(transformer_layers_per_block, int): + transformer_layers_per_block = [transformer_layers_per_block] * num_layers + + # there is always at least one resnet + resnets = [ + ResnetBlock2D( + in_channels=in_channels, + out_channels=in_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ] + attentions = [] + + for i in range(num_layers): + if not dual_cross_attention: + attentions.append( + Transformer2DModel( + num_attention_heads, + in_channels // num_attention_heads, + in_channels=in_channels, + num_layers=transformer_layers_per_block[i], + cross_attention_dim=cross_attention_dim, + norm_num_groups=resnet_groups, + use_linear_projection=use_linear_projection, + upcast_attention=upcast_attention, + attention_type=attention_type, + ) + ) + else: + attentions.append( + DualTransformer2DModel( + num_attention_heads, + in_channels // num_attention_heads, + in_channels=in_channels, + num_layers=1, + cross_attention_dim=cross_attention_dim, + norm_num_groups=resnet_groups, + ) + ) + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=in_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + self.gradient_checkpointing = False + + def forward( + self, + hidden_states: torch.FloatTensor, + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + lora_scale = cross_attention_kwargs.get("scale", 1.0) if cross_attention_kwargs is not None else 1.0 + hidden_states = self.resnets[0](hidden_states, temb, scale=lora_scale) + for attn, resnet in zip(self.attentions, self.resnets[1:]): + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + cross_attention_kwargs=cross_attention_kwargs, + attention_mask=attention_mask, + encoder_attention_mask=encoder_attention_mask, + return_dict=False, + )[0] + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), + hidden_states, + temb, + **ckpt_kwargs, + ) + else: + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + cross_attention_kwargs=cross_attention_kwargs, + attention_mask=attention_mask, + encoder_attention_mask=encoder_attention_mask, + return_dict=False, + )[0] + hidden_states = resnet(hidden_states, temb, scale=lora_scale) + + return hidden_states + + +class UNetMidBlock2DSimpleCrossAttn(nn.Module): + def __init__( + self, + in_channels: int, + temb_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + attention_head_dim: int = 1, + output_scale_factor: float = 1.0, + cross_attention_dim: int = 1280, + skip_time_act: bool = False, + only_cross_attention: bool = False, + cross_attention_norm: Optional[str] = None, + ): + super().__init__() + + self.has_cross_attention = True + + self.attention_head_dim = attention_head_dim + resnet_groups = resnet_groups if resnet_groups is not None else min(in_channels // 4, 32) + + self.num_heads = in_channels // self.attention_head_dim + + # there is always at least one resnet + resnets = [ + ResnetBlock2D( + in_channels=in_channels, + out_channels=in_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + skip_time_act=skip_time_act, + ) + ] + attentions = [] + + for _ in range(num_layers): + processor = ( + AttnAddedKVProcessor2_0() if hasattr(F, "scaled_dot_product_attention") else AttnAddedKVProcessor() + ) + + attentions.append( + Attention( + query_dim=in_channels, + cross_attention_dim=in_channels, + heads=self.num_heads, + dim_head=self.attention_head_dim, + added_kv_proj_dim=cross_attention_dim, + norm_num_groups=resnet_groups, + bias=True, + upcast_softmax=True, + only_cross_attention=only_cross_attention, + cross_attention_norm=cross_attention_norm, + processor=processor, + ) + ) + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=in_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + skip_time_act=skip_time_act, + ) + ) + + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + def forward( + self, + hidden_states: torch.FloatTensor, + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + cross_attention_kwargs = cross_attention_kwargs if cross_attention_kwargs is not None else {} + lora_scale = cross_attention_kwargs.get("scale", 1.0) + + if attention_mask is None: + # if encoder_hidden_states is defined: we are doing cross-attn, so we should use cross-attn mask. + mask = None if encoder_hidden_states is None else encoder_attention_mask + else: + # when attention_mask is defined: we don't even check for encoder_attention_mask. + # this is to maintain compatibility with UnCLIP, which uses 'attention_mask' param for cross-attn masks. + # TODO: UnCLIP should express cross-attn mask via encoder_attention_mask param instead of via attention_mask. + # then we can simplify this whole if/else block to: + # mask = attention_mask if encoder_hidden_states is None else encoder_attention_mask + mask = attention_mask + + hidden_states = self.resnets[0](hidden_states, temb, scale=lora_scale) + for attn, resnet in zip(self.attentions, self.resnets[1:]): + # attn + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=mask, + **cross_attention_kwargs, + ) + + # resnet + hidden_states = resnet(hidden_states, temb, scale=lora_scale) + + return hidden_states + + +class AttnDownBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + temb_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + attention_head_dim: int = 1, + output_scale_factor: float = 1.0, + downsample_padding: int = 1, + downsample_type: str = "conv", + ): + super().__init__() + resnets = [] + attentions = [] + self.downsample_type = downsample_type + + if attention_head_dim is None: + logger.warn( + f"It is not recommend to pass `attention_head_dim=None`. Defaulting `attention_head_dim` to `in_channels`: {out_channels}." + ) + attention_head_dim = out_channels + + for i in range(num_layers): + in_channels = in_channels if i == 0 else out_channels + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + attentions.append( + Attention( + out_channels, + heads=out_channels // attention_head_dim, + dim_head=attention_head_dim, + rescale_output_factor=output_scale_factor, + eps=resnet_eps, + norm_num_groups=resnet_groups, + residual_connection=True, + bias=True, + upcast_softmax=True, + _from_deprecated_attn_block=True, + ) + ) + + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + if downsample_type == "conv": + self.downsamplers = nn.ModuleList( + [ + Downsample2D( + out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op" + ) + ] + ) + elif downsample_type == "resnet": + self.downsamplers = nn.ModuleList( + [ + ResnetBlock2D( + in_channels=out_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + down=True, + ) + ] + ) + else: + self.downsamplers = None + + def forward( + self, + hidden_states: torch.FloatTensor, + temb: Optional[torch.FloatTensor] = None, + upsample_size: Optional[int] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + ) -> Tuple[torch.FloatTensor, Tuple[torch.FloatTensor, ...]]: + cross_attention_kwargs = cross_attention_kwargs if cross_attention_kwargs is not None else {} + + lora_scale = cross_attention_kwargs.get("scale", 1.0) + + output_states = () + + for resnet, attn in zip(self.resnets, self.attentions): + cross_attention_kwargs.update({"scale": lora_scale}) + hidden_states = resnet(hidden_states, temb, scale=lora_scale) + hidden_states = attn(hidden_states, **cross_attention_kwargs) + output_states = output_states + (hidden_states,) + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + if self.downsample_type == "resnet": + hidden_states = downsampler(hidden_states, temb=temb, scale=lora_scale) + else: + hidden_states = downsampler(hidden_states, scale=lora_scale) + + output_states += (hidden_states,) + + return hidden_states, output_states + + +class CrossAttnDownBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + temb_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + transformer_layers_per_block: Union[int, Tuple[int]] = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + num_attention_heads: int = 1, + cross_attention_dim: int = 1280, + output_scale_factor: float = 1.0, + downsample_padding: int = 1, + add_downsample: bool = True, + dual_cross_attention: bool = False, + use_linear_projection: bool = False, + only_cross_attention: bool = False, + upcast_attention: bool = False, + attention_type: str = "default", + ): + super().__init__() + resnets = [] + attentions = [] + + self.has_cross_attention = True + self.num_attention_heads = num_attention_heads + if isinstance(transformer_layers_per_block, int): + transformer_layers_per_block = [transformer_layers_per_block] * num_layers + + for i in range(num_layers): + in_channels = in_channels if i == 0 else out_channels + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + if not dual_cross_attention: + # Transformer2DModelWithSwitcher + attentions.append( + Transformer2DModel( + num_attention_heads, + out_channels // num_attention_heads, + in_channels=out_channels, + num_layers=transformer_layers_per_block[i], + cross_attention_dim=cross_attention_dim, + norm_num_groups=resnet_groups, + use_linear_projection=use_linear_projection, + only_cross_attention=only_cross_attention, + upcast_attention=upcast_attention, + attention_type=attention_type, + ) + ) + else: + attentions.append( + DualTransformer2DModel( + num_attention_heads, + out_channels // num_attention_heads, + in_channels=out_channels, + num_layers=1, + cross_attention_dim=cross_attention_dim, + norm_num_groups=resnet_groups, + ) + ) + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + if add_downsample: + self.downsamplers = nn.ModuleList( + [ + Downsample2D( + out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op" + ) + ] + ) + else: + self.downsamplers = None + + self.gradient_checkpointing = False + + def forward( + self, + hidden_states: torch.FloatTensor, + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + additional_residuals: Optional[torch.FloatTensor] = None, + ) -> Tuple[torch.FloatTensor, Tuple[torch.FloatTensor, ...]]: + output_states = () + + lora_scale = cross_attention_kwargs.get("scale", 1.0) if cross_attention_kwargs is not None else 1.0 + + blocks = list(zip(self.resnets, self.attentions)) + + for i, (resnet, attn) in enumerate(blocks): + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), + hidden_states, + temb, + **ckpt_kwargs, + ) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + cross_attention_kwargs=cross_attention_kwargs, + attention_mask=attention_mask, + encoder_attention_mask=encoder_attention_mask, + return_dict=False, + )[0] + else: + hidden_states = resnet(hidden_states, temb, scale=lora_scale) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + cross_attention_kwargs=cross_attention_kwargs, + attention_mask=attention_mask, + encoder_attention_mask=encoder_attention_mask, + return_dict=False, + )[0] + + # apply additional residuals to the output of the last pair of resnet and attention blocks + if i == len(blocks) - 1 and additional_residuals is not None: + hidden_states = hidden_states + additional_residuals + + output_states = output_states + (hidden_states,) + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states, scale=lora_scale) + + output_states = output_states + (hidden_states,) + + return hidden_states, output_states + + +class DownBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + temb_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + output_scale_factor: float = 1.0, + add_downsample: bool = True, + downsample_padding: int = 1, + ): + super().__init__() + resnets = [] + + for i in range(num_layers): + in_channels = in_channels if i == 0 else out_channels + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + + self.resnets = nn.ModuleList(resnets) + + if add_downsample: + self.downsamplers = nn.ModuleList( + [ + Downsample2D( + out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op" + ) + ] + ) + else: + self.downsamplers = None + + self.gradient_checkpointing = False + + def forward( + self, hidden_states: torch.FloatTensor, temb: Optional[torch.FloatTensor] = None, scale: float = 1.0 + ) -> Tuple[torch.FloatTensor, Tuple[torch.FloatTensor, ...]]: + output_states = () + + for resnet in self.resnets: + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + if is_torch_version(">=", "1.11.0"): + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb, use_reentrant=False + ) + else: + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb + ) + else: + hidden_states = resnet(hidden_states, temb, scale=scale) + + output_states = output_states + (hidden_states,) + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states, scale=scale) + + output_states = output_states + (hidden_states,) + + return hidden_states, output_states + + +class DownEncoderBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + output_scale_factor: float = 1.0, + add_downsample: bool = True, + downsample_padding: int = 1, + ): + super().__init__() + resnets = [] + + for i in range(num_layers): + in_channels = in_channels if i == 0 else out_channels + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + temb_channels=None, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + + self.resnets = nn.ModuleList(resnets) + + if add_downsample: + self.downsamplers = nn.ModuleList( + [ + Downsample2D( + out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op" + ) + ] + ) + else: + self.downsamplers = None + + def forward(self, hidden_states: torch.FloatTensor, scale: float = 1.0) -> torch.FloatTensor: + for resnet in self.resnets: + hidden_states = resnet(hidden_states, temb=None, scale=scale) + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states, scale) + + return hidden_states + + +class AttnDownEncoderBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + attention_head_dim: int = 1, + output_scale_factor: float = 1.0, + add_downsample: bool = True, + downsample_padding: int = 1, + ): + super().__init__() + resnets = [] + attentions = [] + + if attention_head_dim is None: + logger.warn( + f"It is not recommend to pass `attention_head_dim=None`. Defaulting `attention_head_dim` to `in_channels`: {out_channels}." + ) + attention_head_dim = out_channels + + for i in range(num_layers): + in_channels = in_channels if i == 0 else out_channels + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + temb_channels=None, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + attentions.append( + Attention( + out_channels, + heads=out_channels // attention_head_dim, + dim_head=attention_head_dim, + rescale_output_factor=output_scale_factor, + eps=resnet_eps, + norm_num_groups=resnet_groups, + residual_connection=True, + bias=True, + upcast_softmax=True, + _from_deprecated_attn_block=True, + ) + ) + + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + if add_downsample: + self.downsamplers = nn.ModuleList( + [ + Downsample2D( + out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op" + ) + ] + ) + else: + self.downsamplers = None + + def forward(self, hidden_states: torch.FloatTensor, scale: float = 1.0) -> torch.FloatTensor: + for resnet, attn in zip(self.resnets, self.attentions): + hidden_states = resnet(hidden_states, temb=None, scale=scale) + cross_attention_kwargs = {"scale": scale} + hidden_states = attn(hidden_states, **cross_attention_kwargs) + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states, scale) + + return hidden_states + + +class AttnSkipDownBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + temb_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_pre_norm: bool = True, + attention_head_dim: int = 1, + output_scale_factor: float = np.sqrt(2.0), + add_downsample: bool = True, + ): + super().__init__() + self.attentions = nn.ModuleList([]) + self.resnets = nn.ModuleList([]) + + if attention_head_dim is None: + logger.warn( + f"It is not recommend to pass `attention_head_dim=None`. Defaulting `attention_head_dim` to `in_channels`: {out_channels}." + ) + attention_head_dim = out_channels + + for i in range(num_layers): + in_channels = in_channels if i == 0 else out_channels + self.resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=min(in_channels // 4, 32), + groups_out=min(out_channels // 4, 32), + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + self.attentions.append( + Attention( + out_channels, + heads=out_channels // attention_head_dim, + dim_head=attention_head_dim, + rescale_output_factor=output_scale_factor, + eps=resnet_eps, + norm_num_groups=32, + residual_connection=True, + bias=True, + upcast_softmax=True, + _from_deprecated_attn_block=True, + ) + ) + + if add_downsample: + self.resnet_down = ResnetBlock2D( + in_channels=out_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=min(out_channels // 4, 32), + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + use_in_shortcut=True, + down=True, + kernel="fir", + ) + self.downsamplers = nn.ModuleList([FirDownsample2D(out_channels, out_channels=out_channels)]) + self.skip_conv = nn.Conv2d(3, out_channels, kernel_size=(1, 1), stride=(1, 1)) + else: + self.resnet_down = None + self.downsamplers = None + self.skip_conv = None + + def forward( + self, + hidden_states: torch.FloatTensor, + temb: Optional[torch.FloatTensor] = None, + skip_sample: Optional[torch.FloatTensor] = None, + scale: float = 1.0, + ) -> Tuple[torch.FloatTensor, Tuple[torch.FloatTensor, ...], torch.FloatTensor]: + output_states = () + + for resnet, attn in zip(self.resnets, self.attentions): + hidden_states = resnet(hidden_states, temb, scale=scale) + cross_attention_kwargs = {"scale": scale} + hidden_states = attn(hidden_states, **cross_attention_kwargs) + output_states += (hidden_states,) + + if self.downsamplers is not None: + hidden_states = self.resnet_down(hidden_states, temb, scale=scale) + for downsampler in self.downsamplers: + skip_sample = downsampler(skip_sample) + + hidden_states = self.skip_conv(skip_sample) + hidden_states + + output_states += (hidden_states,) + + return hidden_states, output_states, skip_sample + + +class SkipDownBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + temb_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_pre_norm: bool = True, + output_scale_factor: float = np.sqrt(2.0), + add_downsample: bool = True, + downsample_padding: int = 1, + ): + super().__init__() + self.resnets = nn.ModuleList([]) + + for i in range(num_layers): + in_channels = in_channels if i == 0 else out_channels + self.resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=min(in_channels // 4, 32), + groups_out=min(out_channels // 4, 32), + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + + if add_downsample: + self.resnet_down = ResnetBlock2D( + in_channels=out_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=min(out_channels // 4, 32), + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + use_in_shortcut=True, + down=True, + kernel="fir", + ) + self.downsamplers = nn.ModuleList([FirDownsample2D(out_channels, out_channels=out_channels)]) + self.skip_conv = nn.Conv2d(3, out_channels, kernel_size=(1, 1), stride=(1, 1)) + else: + self.resnet_down = None + self.downsamplers = None + self.skip_conv = None + + def forward( + self, + hidden_states: torch.FloatTensor, + temb: Optional[torch.FloatTensor] = None, + skip_sample: Optional[torch.FloatTensor] = None, + scale: float = 1.0, + ) -> Tuple[torch.FloatTensor, Tuple[torch.FloatTensor, ...], torch.FloatTensor]: + output_states = () + + for resnet in self.resnets: + hidden_states = resnet(hidden_states, temb, scale) + output_states += (hidden_states,) + + if self.downsamplers is not None: + hidden_states = self.resnet_down(hidden_states, temb, scale) + for downsampler in self.downsamplers: + skip_sample = downsampler(skip_sample) + + hidden_states = self.skip_conv(skip_sample) + hidden_states + + output_states += (hidden_states,) + + return hidden_states, output_states, skip_sample + + +class ResnetDownsampleBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + temb_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + output_scale_factor: float = 1.0, + add_downsample: bool = True, + skip_time_act: bool = False, + ): + super().__init__() + resnets = [] + + for i in range(num_layers): + in_channels = in_channels if i == 0 else out_channels + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + skip_time_act=skip_time_act, + ) + ) + + self.resnets = nn.ModuleList(resnets) + + if add_downsample: + self.downsamplers = nn.ModuleList( + [ + ResnetBlock2D( + in_channels=out_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + skip_time_act=skip_time_act, + down=True, + ) + ] + ) + else: + self.downsamplers = None + + self.gradient_checkpointing = False + + def forward( + self, hidden_states: torch.FloatTensor, temb: Optional[torch.FloatTensor] = None, scale: float = 1.0 + ) -> Tuple[torch.FloatTensor, Tuple[torch.FloatTensor, ...]]: + output_states = () + + for resnet in self.resnets: + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + if is_torch_version(">=", "1.11.0"): + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb, use_reentrant=False + ) + else: + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb + ) + else: + hidden_states = resnet(hidden_states, temb, scale) + + output_states = output_states + (hidden_states,) + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states, temb, scale) + + output_states = output_states + (hidden_states,) + + return hidden_states, output_states + + +class SimpleCrossAttnDownBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + temb_channels: int, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + attention_head_dim: int = 1, + cross_attention_dim: int = 1280, + output_scale_factor: float = 1.0, + add_downsample: bool = True, + skip_time_act: bool = False, + only_cross_attention: bool = False, + cross_attention_norm: Optional[str] = None, + ): + super().__init__() + + self.has_cross_attention = True + + resnets = [] + attentions = [] + + self.attention_head_dim = attention_head_dim + self.num_heads = out_channels // self.attention_head_dim + + for i in range(num_layers): + in_channels = in_channels if i == 0 else out_channels + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + skip_time_act=skip_time_act, + ) + ) + + processor = ( + AttnAddedKVProcessor2_0() if hasattr(F, "scaled_dot_product_attention") else AttnAddedKVProcessor() + ) + + attentions.append( + Attention( + query_dim=out_channels, + cross_attention_dim=out_channels, + heads=self.num_heads, + dim_head=attention_head_dim, + added_kv_proj_dim=cross_attention_dim, + norm_num_groups=resnet_groups, + bias=True, + upcast_softmax=True, + only_cross_attention=only_cross_attention, + cross_attention_norm=cross_attention_norm, + processor=processor, + ) + ) + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + if add_downsample: + self.downsamplers = nn.ModuleList( + [ + ResnetBlock2D( + in_channels=out_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + skip_time_act=skip_time_act, + down=True, + ) + ] + ) + else: + self.downsamplers = None + + self.gradient_checkpointing = False + + def forward( + self, + hidden_states: torch.FloatTensor, + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ) -> Tuple[torch.FloatTensor, Tuple[torch.FloatTensor, ...]]: + output_states = () + cross_attention_kwargs = cross_attention_kwargs if cross_attention_kwargs is not None else {} + + lora_scale = cross_attention_kwargs.get("scale", 1.0) + + if attention_mask is None: + # if encoder_hidden_states is defined: we are doing cross-attn, so we should use cross-attn mask. + mask = None if encoder_hidden_states is None else encoder_attention_mask + else: + # when attention_mask is defined: we don't even check for encoder_attention_mask. + # this is to maintain compatibility with UnCLIP, which uses 'attention_mask' param for cross-attn masks. + # TODO: UnCLIP should express cross-attn mask via encoder_attention_mask param instead of via attention_mask. + # then we can simplify this whole if/else block to: + # mask = attention_mask if encoder_hidden_states is None else encoder_attention_mask + mask = attention_mask + + for resnet, attn in zip(self.resnets, self.attentions): + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + hidden_states = torch.utils.checkpoint.checkpoint(create_custom_forward(resnet), hidden_states, temb) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=mask, + **cross_attention_kwargs, + ) + else: + hidden_states = resnet(hidden_states, temb, scale=lora_scale) + + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=mask, + **cross_attention_kwargs, + ) + + output_states = output_states + (hidden_states,) + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states, temb, scale=lora_scale) + + output_states = output_states + (hidden_states,) + + return hidden_states, output_states + + +class KDownBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + temb_channels: int, + dropout: float = 0.0, + num_layers: int = 4, + resnet_eps: float = 1e-5, + resnet_act_fn: str = "gelu", + resnet_group_size: int = 32, + add_downsample: bool = False, + ): + super().__init__() + resnets = [] + + for i in range(num_layers): + in_channels = in_channels if i == 0 else out_channels + groups = in_channels // resnet_group_size + groups_out = out_channels // resnet_group_size + + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + dropout=dropout, + temb_channels=temb_channels, + groups=groups, + groups_out=groups_out, + eps=resnet_eps, + non_linearity=resnet_act_fn, + time_embedding_norm="ada_group", + conv_shortcut_bias=False, + ) + ) + + self.resnets = nn.ModuleList(resnets) + + if add_downsample: + # YiYi's comments- might be able to use FirDownsample2D, look into details later + self.downsamplers = nn.ModuleList([KDownsample2D()]) + else: + self.downsamplers = None + + self.gradient_checkpointing = False + + def forward( + self, hidden_states: torch.FloatTensor, temb: Optional[torch.FloatTensor] = None, scale: float = 1.0 + ) -> Tuple[torch.FloatTensor, Tuple[torch.FloatTensor, ...]]: + output_states = () + + for resnet in self.resnets: + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + if is_torch_version(">=", "1.11.0"): + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb, use_reentrant=False + ) + else: + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb + ) + else: + hidden_states = resnet(hidden_states, temb, scale) + + output_states += (hidden_states,) + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states) + + return hidden_states, output_states + + +class KCrossAttnDownBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + temb_channels: int, + cross_attention_dim: int, + dropout: float = 0.0, + num_layers: int = 4, + resnet_group_size: int = 32, + add_downsample: bool = True, + attention_head_dim: int = 64, + add_self_attention: bool = False, + resnet_eps: float = 1e-5, + resnet_act_fn: str = "gelu", + ): + super().__init__() + resnets = [] + attentions = [] + + self.has_cross_attention = True + + for i in range(num_layers): + in_channels = in_channels if i == 0 else out_channels + groups = in_channels // resnet_group_size + groups_out = out_channels // resnet_group_size + + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + dropout=dropout, + temb_channels=temb_channels, + groups=groups, + groups_out=groups_out, + eps=resnet_eps, + non_linearity=resnet_act_fn, + time_embedding_norm="ada_group", + conv_shortcut_bias=False, + ) + ) + attentions.append( + KAttentionBlock( + out_channels, + out_channels // attention_head_dim, + attention_head_dim, + cross_attention_dim=cross_attention_dim, + temb_channels=temb_channels, + attention_bias=True, + add_self_attention=add_self_attention, + cross_attention_norm="layer_norm", + group_size=resnet_group_size, + ) + ) + + self.resnets = nn.ModuleList(resnets) + self.attentions = nn.ModuleList(attentions) + + if add_downsample: + self.downsamplers = nn.ModuleList([KDownsample2D()]) + else: + self.downsamplers = None + + self.gradient_checkpointing = False + + def forward( + self, + hidden_states: torch.FloatTensor, + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ) -> Tuple[torch.FloatTensor, Tuple[torch.FloatTensor, ...]]: + output_states = () + lora_scale = cross_attention_kwargs.get("scale", 1.0) if cross_attention_kwargs is not None else 1.0 + + for resnet, attn in zip(self.resnets, self.attentions): + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), + hidden_states, + temb, + **ckpt_kwargs, + ) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + emb=temb, + attention_mask=attention_mask, + cross_attention_kwargs=cross_attention_kwargs, + encoder_attention_mask=encoder_attention_mask, + ) + else: + hidden_states = resnet(hidden_states, temb, scale=lora_scale) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + emb=temb, + attention_mask=attention_mask, + cross_attention_kwargs=cross_attention_kwargs, + encoder_attention_mask=encoder_attention_mask, + ) + + if self.downsamplers is None: + output_states += (None,) + else: + output_states += (hidden_states,) + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states) + + return hidden_states, output_states + + +class AttnUpBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + prev_output_channel: int, + out_channels: int, + temb_channels: int, + resolution_idx: int = None, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + attention_head_dim: int = 1, + output_scale_factor: float = 1.0, + upsample_type: str = "conv", + ): + super().__init__() + resnets = [] + attentions = [] + + self.upsample_type = upsample_type + + if attention_head_dim is None: + logger.warn( + f"It is not recommend to pass `attention_head_dim=None`. Defaulting `attention_head_dim` to `in_channels`: {out_channels}." + ) + attention_head_dim = out_channels + + for i in range(num_layers): + res_skip_channels = in_channels if (i == num_layers - 1) else out_channels + resnet_in_channels = prev_output_channel if i == 0 else out_channels + + resnets.append( + ResnetBlock2D( + in_channels=resnet_in_channels + res_skip_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + attentions.append( + Attention( + out_channels, + heads=out_channels // attention_head_dim, + dim_head=attention_head_dim, + rescale_output_factor=output_scale_factor, + eps=resnet_eps, + norm_num_groups=resnet_groups, + residual_connection=True, + bias=True, + upcast_softmax=True, + _from_deprecated_attn_block=True, + ) + ) + + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + if upsample_type == "conv": + self.upsamplers = nn.ModuleList([Upsample2D(out_channels, use_conv=True, out_channels=out_channels)]) + elif upsample_type == "resnet": + self.upsamplers = nn.ModuleList( + [ + ResnetBlock2D( + in_channels=out_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + up=True, + ) + ] + ) + else: + self.upsamplers = None + + self.resolution_idx = resolution_idx + + def forward( + self, + hidden_states: torch.FloatTensor, + res_hidden_states_tuple: Tuple[torch.FloatTensor, ...], + temb: Optional[torch.FloatTensor] = None, + upsample_size: Optional[int] = None, + scale: float = 1.0, + ) -> torch.FloatTensor: + for resnet, attn in zip(self.resnets, self.attentions): + # pop res hidden states + res_hidden_states = res_hidden_states_tuple[-1] + res_hidden_states_tuple = res_hidden_states_tuple[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + + hidden_states = resnet(hidden_states, temb, scale=scale) + cross_attention_kwargs = {"scale": scale} + hidden_states = attn(hidden_states, **cross_attention_kwargs) + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + if self.upsample_type == "resnet": + hidden_states = upsampler(hidden_states, temb=temb, scale=scale) + else: + hidden_states = upsampler(hidden_states, scale=scale) + + return hidden_states + + +class CrossAttnUpBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + prev_output_channel: int, + temb_channels: int, + resolution_idx: Optional[int] = None, + dropout: float = 0.0, + num_layers: int = 1, + transformer_layers_per_block: Union[int, Tuple[int]] = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + num_attention_heads: int = 1, + cross_attention_dim: int = 1280, + output_scale_factor: float = 1.0, + add_upsample: bool = True, + dual_cross_attention: bool = False, + use_linear_projection: bool = False, + only_cross_attention: bool = False, + upcast_attention: bool = False, + attention_type: str = "default", + ): + super().__init__() + resnets = [] + attentions = [] + + self.has_cross_attention = True + self.num_attention_heads = num_attention_heads + + if isinstance(transformer_layers_per_block, int): + transformer_layers_per_block = [transformer_layers_per_block] * num_layers + + for i in range(num_layers): + res_skip_channels = in_channels if (i == num_layers - 1) else out_channels + resnet_in_channels = prev_output_channel if i == 0 else out_channels + + resnets.append( + ResnetBlock2D( + in_channels=resnet_in_channels + res_skip_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + if not dual_cross_attention: + # Transformer2DModelWithSwitcher + attentions.append( + Transformer2DModel( + num_attention_heads, + out_channels // num_attention_heads, + in_channels=out_channels, + num_layers=transformer_layers_per_block[i], + cross_attention_dim=cross_attention_dim, + norm_num_groups=resnet_groups, + use_linear_projection=use_linear_projection, + only_cross_attention=only_cross_attention, + upcast_attention=upcast_attention, + attention_type=attention_type, + ) + ) + else: + attentions.append( + DualTransformer2DModel( + num_attention_heads, + out_channels // num_attention_heads, + in_channels=out_channels, + num_layers=1, + cross_attention_dim=cross_attention_dim, + norm_num_groups=resnet_groups, + ) + ) + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + if add_upsample: + self.upsamplers = nn.ModuleList([Upsample2D(out_channels, use_conv=True, out_channels=out_channels)]) + else: + self.upsamplers = None + + self.gradient_checkpointing = False + self.resolution_idx = resolution_idx + + def forward( + self, + hidden_states: torch.FloatTensor, + res_hidden_states_tuple: Tuple[torch.FloatTensor, ...], + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + upsample_size: Optional[int] = None, + attention_mask: Optional[torch.FloatTensor] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + lora_scale = cross_attention_kwargs.get("scale", 1.0) if cross_attention_kwargs is not None else 1.0 + is_freeu_enabled = ( + getattr(self, "s1", None) + and getattr(self, "s2", None) + and getattr(self, "b1", None) + and getattr(self, "b2", None) + ) + + for resnet, attn in zip(self.resnets, self.attentions): + # pop res hidden states + res_hidden_states = res_hidden_states_tuple[-1] + res_hidden_states_tuple = res_hidden_states_tuple[:-1] + + # FreeU: Only operate on the first two stages + if is_freeu_enabled: + hidden_states, res_hidden_states = apply_freeu( + self.resolution_idx, + hidden_states, + res_hidden_states, + s1=self.s1, + s2=self.s2, + b1=self.b1, + b2=self.b2, + ) + + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), + hidden_states, + temb, + **ckpt_kwargs, + ) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + cross_attention_kwargs=cross_attention_kwargs, + attention_mask=attention_mask, + encoder_attention_mask=encoder_attention_mask, + return_dict=False, + )[0] + else: + hidden_states = resnet(hidden_states, temb, scale=lora_scale) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + cross_attention_kwargs=cross_attention_kwargs, + attention_mask=attention_mask, + encoder_attention_mask=encoder_attention_mask, + return_dict=False, + )[0] + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states, upsample_size, scale=lora_scale) + + return hidden_states + + +class UpBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + prev_output_channel: int, + out_channels: int, + temb_channels: int, + resolution_idx: Optional[int] = None, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + output_scale_factor: float = 1.0, + add_upsample: bool = True, + ): + super().__init__() + resnets = [] + + for i in range(num_layers): + res_skip_channels = in_channels if (i == num_layers - 1) else out_channels + resnet_in_channels = prev_output_channel if i == 0 else out_channels + + resnets.append( + ResnetBlock2D( + in_channels=resnet_in_channels + res_skip_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + + self.resnets = nn.ModuleList(resnets) + + if add_upsample: + self.upsamplers = nn.ModuleList([Upsample2D(out_channels, use_conv=True, out_channels=out_channels)]) + else: + self.upsamplers = None + + self.gradient_checkpointing = False + self.resolution_idx = resolution_idx + + def forward( + self, + hidden_states: torch.FloatTensor, + res_hidden_states_tuple: Tuple[torch.FloatTensor, ...], + temb: Optional[torch.FloatTensor] = None, + upsample_size: Optional[int] = None, + scale: float = 1.0, + ) -> torch.FloatTensor: + is_freeu_enabled = ( + getattr(self, "s1", None) + and getattr(self, "s2", None) + and getattr(self, "b1", None) + and getattr(self, "b2", None) + ) + + for resnet in self.resnets: + # pop res hidden states + res_hidden_states = res_hidden_states_tuple[-1] + res_hidden_states_tuple = res_hidden_states_tuple[:-1] + + # FreeU: Only operate on the first two stages + if is_freeu_enabled: + hidden_states, res_hidden_states = apply_freeu( + self.resolution_idx, + hidden_states, + res_hidden_states, + s1=self.s1, + s2=self.s2, + b1=self.b1, + b2=self.b2, + ) + + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + if is_torch_version(">=", "1.11.0"): + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb, use_reentrant=False + ) + else: + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb + ) + else: + hidden_states = resnet(hidden_states, temb, scale=scale) + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states, upsample_size, scale=scale) + + return hidden_states + + +class UpDecoderBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + resolution_idx: Optional[int] = None, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", # default, spatial + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + output_scale_factor: float = 1.0, + add_upsample: bool = True, + temb_channels: Optional[int] = None, + ): + super().__init__() + resnets = [] + + for i in range(num_layers): + input_channels = in_channels if i == 0 else out_channels + + resnets.append( + ResnetBlock2D( + in_channels=input_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + + self.resnets = nn.ModuleList(resnets) + + if add_upsample: + self.upsamplers = nn.ModuleList([Upsample2D(out_channels, use_conv=True, out_channels=out_channels)]) + else: + self.upsamplers = None + + self.resolution_idx = resolution_idx + + def forward( + self, hidden_states: torch.FloatTensor, temb: Optional[torch.FloatTensor] = None, scale: float = 1.0 + ) -> torch.FloatTensor: + for resnet in self.resnets: + hidden_states = resnet(hidden_states, temb=temb, scale=scale) + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states) + + return hidden_states + + +class AttnUpDecoderBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + resolution_idx: Optional[int] = None, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + attention_head_dim: int = 1, + output_scale_factor: float = 1.0, + add_upsample: bool = True, + temb_channels: Optional[int] = None, + ): + super().__init__() + resnets = [] + attentions = [] + + if attention_head_dim is None: + logger.warn( + f"It is not recommend to pass `attention_head_dim=None`. Defaulting `attention_head_dim` to `out_channels`: {out_channels}." + ) + attention_head_dim = out_channels + + for i in range(num_layers): + input_channels = in_channels if i == 0 else out_channels + + resnets.append( + ResnetBlock2D( + in_channels=input_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + attentions.append( + Attention( + out_channels, + heads=out_channels // attention_head_dim, + dim_head=attention_head_dim, + rescale_output_factor=output_scale_factor, + eps=resnet_eps, + norm_num_groups=resnet_groups if resnet_time_scale_shift != "spatial" else None, + spatial_norm_dim=temb_channels if resnet_time_scale_shift == "spatial" else None, + residual_connection=True, + bias=True, + upcast_softmax=True, + _from_deprecated_attn_block=True, + ) + ) + + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + if add_upsample: + self.upsamplers = nn.ModuleList([Upsample2D(out_channels, use_conv=True, out_channels=out_channels)]) + else: + self.upsamplers = None + + self.resolution_idx = resolution_idx + + def forward( + self, hidden_states: torch.FloatTensor, temb: Optional[torch.FloatTensor] = None, scale: float = 1.0 + ) -> torch.FloatTensor: + for resnet, attn in zip(self.resnets, self.attentions): + hidden_states = resnet(hidden_states, temb=temb, scale=scale) + cross_attention_kwargs = {"scale": scale} + hidden_states = attn(hidden_states, temb=temb, **cross_attention_kwargs) + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states, scale=scale) + + return hidden_states + + +class AttnSkipUpBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + prev_output_channel: int, + out_channels: int, + temb_channels: int, + resolution_idx: Optional[int] = None, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_pre_norm: bool = True, + attention_head_dim: int = 1, + output_scale_factor: float = np.sqrt(2.0), + add_upsample: bool = True, + ): + super().__init__() + self.attentions = nn.ModuleList([]) + self.resnets = nn.ModuleList([]) + + for i in range(num_layers): + res_skip_channels = in_channels if (i == num_layers - 1) else out_channels + resnet_in_channels = prev_output_channel if i == 0 else out_channels + + self.resnets.append( + ResnetBlock2D( + in_channels=resnet_in_channels + res_skip_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=min(resnet_in_channels + res_skip_channels // 4, 32), + groups_out=min(out_channels // 4, 32), + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + + if attention_head_dim is None: + logger.warn( + f"It is not recommend to pass `attention_head_dim=None`. Defaulting `attention_head_dim` to `out_channels`: {out_channels}." + ) + attention_head_dim = out_channels + + self.attentions.append( + Attention( + out_channels, + heads=out_channels // attention_head_dim, + dim_head=attention_head_dim, + rescale_output_factor=output_scale_factor, + eps=resnet_eps, + norm_num_groups=32, + residual_connection=True, + bias=True, + upcast_softmax=True, + _from_deprecated_attn_block=True, + ) + ) + + self.upsampler = FirUpsample2D(in_channels, out_channels=out_channels) + if add_upsample: + self.resnet_up = ResnetBlock2D( + in_channels=out_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=min(out_channels // 4, 32), + groups_out=min(out_channels // 4, 32), + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + use_in_shortcut=True, + up=True, + kernel="fir", + ) + self.skip_conv = nn.Conv2d(out_channels, 3, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) + self.skip_norm = torch.nn.GroupNorm( + num_groups=min(out_channels // 4, 32), num_channels=out_channels, eps=resnet_eps, affine=True + ) + self.act = nn.SiLU() + else: + self.resnet_up = None + self.skip_conv = None + self.skip_norm = None + self.act = None + + self.resolution_idx = resolution_idx + + def forward( + self, + hidden_states: torch.FloatTensor, + res_hidden_states_tuple: Tuple[torch.FloatTensor, ...], + temb: Optional[torch.FloatTensor] = None, + skip_sample=None, + scale: float = 1.0, + ) -> Tuple[torch.FloatTensor, torch.FloatTensor]: + for resnet in self.resnets: + # pop res hidden states + res_hidden_states = res_hidden_states_tuple[-1] + res_hidden_states_tuple = res_hidden_states_tuple[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + + hidden_states = resnet(hidden_states, temb, scale=scale) + + cross_attention_kwargs = {"scale": scale} + hidden_states = self.attentions[0](hidden_states, **cross_attention_kwargs) + + if skip_sample is not None: + skip_sample = self.upsampler(skip_sample) + else: + skip_sample = 0 + + if self.resnet_up is not None: + skip_sample_states = self.skip_norm(hidden_states) + skip_sample_states = self.act(skip_sample_states) + skip_sample_states = self.skip_conv(skip_sample_states) + + skip_sample = skip_sample + skip_sample_states + + hidden_states = self.resnet_up(hidden_states, temb, scale=scale) + + return hidden_states, skip_sample + + +class SkipUpBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + prev_output_channel: int, + out_channels: int, + temb_channels: int, + resolution_idx: Optional[int] = None, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_pre_norm: bool = True, + output_scale_factor: float = np.sqrt(2.0), + add_upsample: bool = True, + upsample_padding: int = 1, + ): + super().__init__() + self.resnets = nn.ModuleList([]) + + for i in range(num_layers): + res_skip_channels = in_channels if (i == num_layers - 1) else out_channels + resnet_in_channels = prev_output_channel if i == 0 else out_channels + + self.resnets.append( + ResnetBlock2D( + in_channels=resnet_in_channels + res_skip_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=min((resnet_in_channels + res_skip_channels) // 4, 32), + groups_out=min(out_channels // 4, 32), + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + ) + ) + + self.upsampler = FirUpsample2D(in_channels, out_channels=out_channels) + if add_upsample: + self.resnet_up = ResnetBlock2D( + in_channels=out_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=min(out_channels // 4, 32), + groups_out=min(out_channels // 4, 32), + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + use_in_shortcut=True, + up=True, + kernel="fir", + ) + self.skip_conv = nn.Conv2d(out_channels, 3, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) + self.skip_norm = torch.nn.GroupNorm( + num_groups=min(out_channels // 4, 32), num_channels=out_channels, eps=resnet_eps, affine=True + ) + self.act = nn.SiLU() + else: + self.resnet_up = None + self.skip_conv = None + self.skip_norm = None + self.act = None + + self.resolution_idx = resolution_idx + + def forward( + self, + hidden_states: torch.FloatTensor, + res_hidden_states_tuple: Tuple[torch.FloatTensor, ...], + temb: Optional[torch.FloatTensor] = None, + skip_sample=None, + scale: float = 1.0, + ) -> Tuple[torch.FloatTensor, torch.FloatTensor]: + for resnet in self.resnets: + # pop res hidden states + res_hidden_states = res_hidden_states_tuple[-1] + res_hidden_states_tuple = res_hidden_states_tuple[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + + hidden_states = resnet(hidden_states, temb, scale=scale) + + if skip_sample is not None: + skip_sample = self.upsampler(skip_sample) + else: + skip_sample = 0 + + if self.resnet_up is not None: + skip_sample_states = self.skip_norm(hidden_states) + skip_sample_states = self.act(skip_sample_states) + skip_sample_states = self.skip_conv(skip_sample_states) + + skip_sample = skip_sample + skip_sample_states + + hidden_states = self.resnet_up(hidden_states, temb, scale=scale) + + return hidden_states, skip_sample + + +class ResnetUpsampleBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + prev_output_channel: int, + out_channels: int, + temb_channels: int, + resolution_idx: Optional[int] = None, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + output_scale_factor: float = 1.0, + add_upsample: bool = True, + skip_time_act: bool = False, + ): + super().__init__() + resnets = [] + + for i in range(num_layers): + res_skip_channels = in_channels if (i == num_layers - 1) else out_channels + resnet_in_channels = prev_output_channel if i == 0 else out_channels + + resnets.append( + ResnetBlock2D( + in_channels=resnet_in_channels + res_skip_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + skip_time_act=skip_time_act, + ) + ) + + self.resnets = nn.ModuleList(resnets) + + if add_upsample: + self.upsamplers = nn.ModuleList( + [ + ResnetBlock2D( + in_channels=out_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + skip_time_act=skip_time_act, + up=True, + ) + ] + ) + else: + self.upsamplers = None + + self.gradient_checkpointing = False + self.resolution_idx = resolution_idx + + def forward( + self, + hidden_states: torch.FloatTensor, + res_hidden_states_tuple: Tuple[torch.FloatTensor, ...], + temb: Optional[torch.FloatTensor] = None, + upsample_size: Optional[int] = None, + scale: float = 1.0, + ) -> torch.FloatTensor: + for resnet in self.resnets: + # pop res hidden states + res_hidden_states = res_hidden_states_tuple[-1] + res_hidden_states_tuple = res_hidden_states_tuple[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + if is_torch_version(">=", "1.11.0"): + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb, use_reentrant=False + ) + else: + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb + ) + else: + hidden_states = resnet(hidden_states, temb, scale=scale) + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states, temb, scale=scale) + + return hidden_states + + +class SimpleCrossAttnUpBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + prev_output_channel: int, + temb_channels: int, + resolution_idx: Optional[int] = None, + dropout: float = 0.0, + num_layers: int = 1, + resnet_eps: float = 1e-6, + resnet_time_scale_shift: str = "default", + resnet_act_fn: str = "swish", + resnet_groups: int = 32, + resnet_pre_norm: bool = True, + attention_head_dim: int = 1, + cross_attention_dim: int = 1280, + output_scale_factor: float = 1.0, + add_upsample: bool = True, + skip_time_act: bool = False, + only_cross_attention: bool = False, + cross_attention_norm: Optional[str] = None, + ): + super().__init__() + resnets = [] + attentions = [] + + self.has_cross_attention = True + self.attention_head_dim = attention_head_dim + + self.num_heads = out_channels // self.attention_head_dim + + for i in range(num_layers): + res_skip_channels = in_channels if (i == num_layers - 1) else out_channels + resnet_in_channels = prev_output_channel if i == 0 else out_channels + + resnets.append( + ResnetBlock2D( + in_channels=resnet_in_channels + res_skip_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + skip_time_act=skip_time_act, + ) + ) + + processor = ( + AttnAddedKVProcessor2_0() if hasattr(F, "scaled_dot_product_attention") else AttnAddedKVProcessor() + ) + + attentions.append( + Attention( + query_dim=out_channels, + cross_attention_dim=out_channels, + heads=self.num_heads, + dim_head=self.attention_head_dim, + added_kv_proj_dim=cross_attention_dim, + norm_num_groups=resnet_groups, + bias=True, + upcast_softmax=True, + only_cross_attention=only_cross_attention, + cross_attention_norm=cross_attention_norm, + processor=processor, + ) + ) + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + if add_upsample: + self.upsamplers = nn.ModuleList( + [ + ResnetBlock2D( + in_channels=out_channels, + out_channels=out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=resnet_groups, + dropout=dropout, + time_embedding_norm=resnet_time_scale_shift, + non_linearity=resnet_act_fn, + output_scale_factor=output_scale_factor, + pre_norm=resnet_pre_norm, + skip_time_act=skip_time_act, + up=True, + ) + ] + ) + else: + self.upsamplers = None + + self.gradient_checkpointing = False + self.resolution_idx = resolution_idx + + def forward( + self, + hidden_states: torch.FloatTensor, + res_hidden_states_tuple: Tuple[torch.FloatTensor, ...], + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + upsample_size: Optional[int] = None, + attention_mask: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + cross_attention_kwargs = cross_attention_kwargs if cross_attention_kwargs is not None else {} + + lora_scale = cross_attention_kwargs.get("scale", 1.0) + if attention_mask is None: + # if encoder_hidden_states is defined: we are doing cross-attn, so we should use cross-attn mask. + mask = None if encoder_hidden_states is None else encoder_attention_mask + else: + # when attention_mask is defined: we don't even check for encoder_attention_mask. + # this is to maintain compatibility with UnCLIP, which uses 'attention_mask' param for cross-attn masks. + # TODO: UnCLIP should express cross-attn mask via encoder_attention_mask param instead of via attention_mask. + # then we can simplify this whole if/else block to: + # mask = attention_mask if encoder_hidden_states is None else encoder_attention_mask + mask = attention_mask + + for resnet, attn in zip(self.resnets, self.attentions): + # resnet + # pop res hidden states + res_hidden_states = res_hidden_states_tuple[-1] + res_hidden_states_tuple = res_hidden_states_tuple[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + hidden_states = torch.utils.checkpoint.checkpoint(create_custom_forward(resnet), hidden_states, temb) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=mask, + **cross_attention_kwargs, + ) + else: + hidden_states = resnet(hidden_states, temb, scale=lora_scale) + + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=mask, + **cross_attention_kwargs, + ) + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states, temb, scale=lora_scale) + + return hidden_states + + +class KUpBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + temb_channels: int, + resolution_idx: int, + dropout: float = 0.0, + num_layers: int = 5, + resnet_eps: float = 1e-5, + resnet_act_fn: str = "gelu", + resnet_group_size: Optional[int] = 32, + add_upsample: bool = True, + ): + super().__init__() + resnets = [] + k_in_channels = 2 * out_channels + k_out_channels = in_channels + num_layers = num_layers - 1 + + for i in range(num_layers): + in_channels = k_in_channels if i == 0 else out_channels + groups = in_channels // resnet_group_size + groups_out = out_channels // resnet_group_size + + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=k_out_channels if (i == num_layers - 1) else out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=groups, + groups_out=groups_out, + dropout=dropout, + non_linearity=resnet_act_fn, + time_embedding_norm="ada_group", + conv_shortcut_bias=False, + ) + ) + + self.resnets = nn.ModuleList(resnets) + + if add_upsample: + self.upsamplers = nn.ModuleList([KUpsample2D()]) + else: + self.upsamplers = None + + self.gradient_checkpointing = False + self.resolution_idx = resolution_idx + + def forward( + self, + hidden_states: torch.FloatTensor, + res_hidden_states_tuple: Tuple[torch.FloatTensor, ...], + temb: Optional[torch.FloatTensor] = None, + upsample_size: Optional[int] = None, + scale: float = 1.0, + ) -> torch.FloatTensor: + res_hidden_states_tuple = res_hidden_states_tuple[-1] + if res_hidden_states_tuple is not None: + hidden_states = torch.cat([hidden_states, res_hidden_states_tuple], dim=1) + + for resnet in self.resnets: + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + if is_torch_version(">=", "1.11.0"): + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb, use_reentrant=False + ) + else: + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), hidden_states, temb + ) + else: + hidden_states = resnet(hidden_states, temb, scale=scale) + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states) + + return hidden_states + + +class KCrossAttnUpBlock2D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + temb_channels: int, + resolution_idx: int, + dropout: float = 0.0, + num_layers: int = 4, + resnet_eps: float = 1e-5, + resnet_act_fn: str = "gelu", + resnet_group_size: int = 32, + attention_head_dim: int = 1, # attention dim_head + cross_attention_dim: int = 768, + add_upsample: bool = True, + upcast_attention: bool = False, + ): + super().__init__() + resnets = [] + attentions = [] + + is_first_block = in_channels == out_channels == temb_channels + is_middle_block = in_channels != out_channels + add_self_attention = True if is_first_block else False + + self.has_cross_attention = True + self.attention_head_dim = attention_head_dim + + # in_channels, and out_channels for the block (k-unet) + k_in_channels = out_channels if is_first_block else 2 * out_channels + k_out_channels = in_channels + + num_layers = num_layers - 1 + + for i in range(num_layers): + in_channels = k_in_channels if i == 0 else out_channels + groups = in_channels // resnet_group_size + groups_out = out_channels // resnet_group_size + + if is_middle_block and (i == num_layers - 1): + conv_2d_out_channels = k_out_channels + else: + conv_2d_out_channels = None + + resnets.append( + ResnetBlock2D( + in_channels=in_channels, + out_channels=out_channels, + conv_2d_out_channels=conv_2d_out_channels, + temb_channels=temb_channels, + eps=resnet_eps, + groups=groups, + groups_out=groups_out, + dropout=dropout, + non_linearity=resnet_act_fn, + time_embedding_norm="ada_group", + conv_shortcut_bias=False, + ) + ) + attentions.append( + KAttentionBlock( + k_out_channels if (i == num_layers - 1) else out_channels, + k_out_channels // attention_head_dim + if (i == num_layers - 1) + else out_channels // attention_head_dim, + attention_head_dim, + cross_attention_dim=cross_attention_dim, + temb_channels=temb_channels, + attention_bias=True, + add_self_attention=add_self_attention, + cross_attention_norm="layer_norm", + upcast_attention=upcast_attention, + ) + ) + + self.resnets = nn.ModuleList(resnets) + self.attentions = nn.ModuleList(attentions) + + if add_upsample: + self.upsamplers = nn.ModuleList([KUpsample2D()]) + else: + self.upsamplers = None + + self.gradient_checkpointing = False + self.resolution_idx = resolution_idx + + def forward( + self, + hidden_states: torch.FloatTensor, + res_hidden_states_tuple: Tuple[torch.FloatTensor, ...], + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + upsample_size: Optional[int] = None, + attention_mask: Optional[torch.FloatTensor] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + res_hidden_states_tuple = res_hidden_states_tuple[-1] + if res_hidden_states_tuple is not None: + hidden_states = torch.cat([hidden_states, res_hidden_states_tuple], dim=1) + + lora_scale = cross_attention_kwargs.get("scale", 1.0) if cross_attention_kwargs is not None else 1.0 + for resnet, attn in zip(self.resnets, self.attentions): + if self.training and self.gradient_checkpointing: + + def create_custom_forward(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(resnet), + hidden_states, + temb, + **ckpt_kwargs, + ) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + emb=temb, + attention_mask=attention_mask, + cross_attention_kwargs=cross_attention_kwargs, + encoder_attention_mask=encoder_attention_mask, + ) + else: + hidden_states = resnet(hidden_states, temb, scale=lora_scale) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + emb=temb, + attention_mask=attention_mask, + cross_attention_kwargs=cross_attention_kwargs, + encoder_attention_mask=encoder_attention_mask, + ) + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states) + + return hidden_states + + +# can potentially later be renamed to `No-feed-forward` attention +class KAttentionBlock(nn.Module): + r""" + A basic Transformer block. + + Parameters: + dim (`int`): The number of channels in the input and output. + num_attention_heads (`int`): The number of heads to use for multi-head attention. + attention_head_dim (`int`): The number of channels in each head. + dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use. + cross_attention_dim (`int`, *optional*): The size of the encoder_hidden_states vector for cross attention. + attention_bias (`bool`, *optional*, defaults to `False`): + Configure if the attention layers should contain a bias parameter. + upcast_attention (`bool`, *optional*, defaults to `False`): + Set to `True` to upcast the attention computation to `float32`. + temb_channels (`int`, *optional*, defaults to 768): + The number of channels in the token embedding. + add_self_attention (`bool`, *optional*, defaults to `False`): + Set to `True` to add self-attention to the block. + cross_attention_norm (`str`, *optional*, defaults to `None`): + The type of normalization to use for the cross attention. Can be `None`, `layer_norm`, or `group_norm`. + group_size (`int`, *optional*, defaults to 32): + The number of groups to separate the channels into for group normalization. + """ + + def __init__( + self, + dim: int, + num_attention_heads: int, + attention_head_dim: int, + dropout: float = 0.0, + cross_attention_dim: Optional[int] = None, + attention_bias: bool = False, + upcast_attention: bool = False, + temb_channels: int = 768, # for ada_group_norm + add_self_attention: bool = False, + cross_attention_norm: Optional[str] = None, + group_size: int = 32, + ): + super().__init__() + self.add_self_attention = add_self_attention + + # 1. Self-Attn + if add_self_attention: + self.norm1 = AdaGroupNorm(temb_channels, dim, max(1, dim // group_size)) + self.attn1 = Attention( + query_dim=dim, + heads=num_attention_heads, + dim_head=attention_head_dim, + dropout=dropout, + bias=attention_bias, + cross_attention_dim=None, + cross_attention_norm=None, + ) + + # 2. Cross-Attn + self.norm2 = AdaGroupNorm(temb_channels, dim, max(1, dim // group_size)) + self.attn2 = Attention( + query_dim=dim, + cross_attention_dim=cross_attention_dim, + heads=num_attention_heads, + dim_head=attention_head_dim, + dropout=dropout, + bias=attention_bias, + upcast_attention=upcast_attention, + cross_attention_norm=cross_attention_norm, + ) + + def _to_3d(self, hidden_states: torch.FloatTensor, height: int, weight: int) -> torch.FloatTensor: + return hidden_states.permute(0, 2, 3, 1).reshape(hidden_states.shape[0], height * weight, -1) + + def _to_4d(self, hidden_states: torch.FloatTensor, height: int, weight: int) -> torch.FloatTensor: + return hidden_states.permute(0, 2, 1).reshape(hidden_states.shape[0], -1, height, weight) + + def forward( + self, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + # TODO: mark emb as non-optional (self.norm2 requires it). + # requires assessing impact of change to positional param interface. + emb: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + cross_attention_kwargs = cross_attention_kwargs if cross_attention_kwargs is not None else {} + + # 1. Self-Attention + if self.add_self_attention: + norm_hidden_states = self.norm1(hidden_states, emb) + + height, weight = norm_hidden_states.shape[2:] + norm_hidden_states = self._to_3d(norm_hidden_states, height, weight) + + attn_output = self.attn1( + norm_hidden_states, + encoder_hidden_states=None, + attention_mask=attention_mask, + **cross_attention_kwargs, + ) + attn_output = self._to_4d(attn_output, height, weight) + + hidden_states = attn_output + hidden_states + + # 2. Cross-Attention/None + norm_hidden_states = self.norm2(hidden_states, emb) + + height, weight = norm_hidden_states.shape[2:] + norm_hidden_states = self._to_3d(norm_hidden_states, height, weight) + attn_output = self.attn2( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=attention_mask if encoder_hidden_states is None else encoder_attention_mask, + **cross_attention_kwargs, + ) + attn_output = self._to_4d(attn_output, height, weight) + + hidden_states = attn_output + hidden_states + + return hidden_states \ No newline at end of file diff --git a/foleycrafter/models/auffusion_unet.py b/foleycrafter/models/auffusion_unet.py new file mode 100644 index 0000000000000000000000000000000000000000..508b89dacd0ce137a8f1767397d07925b0daab01 --- /dev/null +++ b/foleycrafter/models/auffusion_unet.py @@ -0,0 +1,1260 @@ +# Copyright 2023 The HuggingFace 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 +# limitations under the License. +from dataclasses import dataclass +from typing import Any, Dict, List, Optional, Tuple, Union + +import torch +import torch.nn as nn +import torch.utils.checkpoint + +from diffusers.configuration_utils import ConfigMixin, register_to_config +from diffusers.utils.import_utils import is_xformers_available, is_torch_version +from diffusers.utils import USE_PEFT_BACKEND, BaseOutput, deprecate, logging, scale_lora_layers, unscale_lora_layers +from diffusers.models.activations import get_activation +# from diffusers import StableDiffusionGLIGENPipeline +from diffusers.models.attention_processor import ( + ADDED_KV_ATTENTION_PROCESSORS, + CROSS_ATTENTION_PROCESSORS, + Attention, + AttentionProcessor, + AttnAddedKVProcessor, + AttnProcessor, + XFormersAttnProcessor, +) +from diffusers.models.embeddings import ( + GaussianFourierProjection, + ImageHintTimeEmbedding, + ImageProjection, + ImageTimeEmbedding, + PositionNet, + TextImageProjection, + TextImageTimeEmbedding, + TextTimeEmbedding, + TimestepEmbedding, + Timesteps, +) +from diffusers.models.modeling_utils import ModelMixin + +from foleycrafter.models.auffusion.unet_2d_blocks import ( + UNetMidBlock2D, + UNetMidBlock2DCrossAttn, + UNetMidBlock2DSimpleCrossAttn, + get_down_block, + get_up_block, +) + +from foleycrafter.models.auffusion.attention_processor\ + import AttnProcessor2_0 +from foleycrafter.models.adapters.ip_adapter import TimeProjModel +from foleycrafter.models.auffusion.loaders.unet import UNet2DConditionLoadersMixin + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + +@dataclass +class UNet2DConditionOutput(BaseOutput): + """ + The output of [`UNet2DConditionModel`]. + + Args: + sample (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): + The hidden states output conditioned on `encoder_hidden_states` input. Output of last layer of model. + """ + + sample: torch.FloatTensor = None + + +class UNet2DConditionModel(ModelMixin, ConfigMixin, UNet2DConditionLoadersMixin): + r""" + A conditional 2D UNet model that takes a noisy sample, conditional state, and a timestep and returns a sample + shaped output. + + This model inherits from [`ModelMixin`]. Check the superclass documentation for it's generic methods implemented + for all models (such as downloading or saving). + + Parameters: + sample_size (`int` or `Tuple[int, int]`, *optional*, defaults to `None`): + Height and width of input/output sample. + in_channels (`int`, *optional*, defaults to 4): Number of channels in the input sample. + out_channels (`int`, *optional*, defaults to 4): Number of channels in the output. + center_input_sample (`bool`, *optional*, defaults to `False`): Whether to center the input sample. + flip_sin_to_cos (`bool`, *optional*, defaults to `False`): + Whether to flip the sin to cos in the time embedding. + freq_shift (`int`, *optional*, defaults to 0): The frequency shift to apply to the time embedding. + down_block_types (`Tuple[str]`, *optional*, defaults to `("CrossAttnDownBlock2D", "CrossAttnDownBlock2D", "CrossAttnDownBlock2D", "DownBlock2D")`): + The tuple of downsample blocks to use. + mid_block_type (`str`, *optional*, defaults to `"UNetMidBlock2DCrossAttn"`): + Block type for middle of UNet, it can be one of `UNetMidBlock2DCrossAttn`, `UNetMidBlock2D`, or + `UNetMidBlock2DSimpleCrossAttn`. If `None`, the mid block layer is skipped. + up_block_types (`Tuple[str]`, *optional*, defaults to `("UpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D")`): + The tuple of upsample blocks to use. + only_cross_attention(`bool` or `Tuple[bool]`, *optional*, default to `False`): + Whether to include self-attention in the basic transformer blocks, see + [`~models.attention.BasicTransformerBlock`]. + block_out_channels (`Tuple[int]`, *optional*, defaults to `(320, 640, 1280, 1280)`): + The tuple of output channels for each block. + layers_per_block (`int`, *optional*, defaults to 2): The number of layers per block. + downsample_padding (`int`, *optional*, defaults to 1): The padding to use for the downsampling convolution. + mid_block_scale_factor (`float`, *optional*, defaults to 1.0): The scale factor to use for the mid block. + dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use. + act_fn (`str`, *optional*, defaults to `"silu"`): The activation function to use. + norm_num_groups (`int`, *optional*, defaults to 32): The number of groups to use for the normalization. + If `None`, normalization and activation layers is skipped in post-processing. + norm_eps (`float`, *optional*, defaults to 1e-5): The epsilon to use for the normalization. + cross_attention_dim (`int` or `Tuple[int]`, *optional*, defaults to 1280): + The dimension of the cross attention features. + transformer_layers_per_block (`int`, `Tuple[int]`, or `Tuple[Tuple]` , *optional*, defaults to 1): + The number of transformer blocks of type [`~models.attention.BasicTransformerBlock`]. Only relevant for + [`~models.unet_2d_blocks.CrossAttnDownBlock2D`], [`~models.unet_2d_blocks.CrossAttnUpBlock2D`], + [`~models.unet_2d_blocks.UNetMidBlock2DCrossAttn`]. + reverse_transformer_layers_per_block : (`Tuple[Tuple]`, *optional*, defaults to None): + The number of transformer blocks of type [`~models.attention.BasicTransformerBlock`], in the upsampling + blocks of the U-Net. Only relevant if `transformer_layers_per_block` is of type `Tuple[Tuple]` and for + [`~models.unet_2d_blocks.CrossAttnDownBlock2D`], [`~models.unet_2d_blocks.CrossAttnUpBlock2D`], + [`~models.unet_2d_blocks.UNetMidBlock2DCrossAttn`]. + encoder_hid_dim (`int`, *optional*, defaults to None): + If `encoder_hid_dim_type` is defined, `encoder_hidden_states` will be projected from `encoder_hid_dim` + dimension to `cross_attention_dim`. + encoder_hid_dim_type (`str`, *optional*, defaults to `None`): + If given, the `encoder_hidden_states` and potentially other embeddings are down-projected to text + embeddings of dimension `cross_attention` according to `encoder_hid_dim_type`. + attention_head_dim (`int`, *optional*, defaults to 8): The dimension of the attention heads. + num_attention_heads (`int`, *optional*): + The number of attention heads. If not defined, defaults to `attention_head_dim` + resnet_time_scale_shift (`str`, *optional*, defaults to `"default"`): Time scale shift config + for ResNet blocks (see [`~models.resnet.ResnetBlock2D`]). Choose from `default` or `scale_shift`. + class_embed_type (`str`, *optional*, defaults to `None`): + The type of class embedding to use which is ultimately summed with the time embeddings. Choose from `None`, + `"timestep"`, `"identity"`, `"projection"`, or `"simple_projection"`. + addition_embed_type (`str`, *optional*, defaults to `None`): + Configures an optional embedding which will be summed with the time embeddings. Choose from `None` or + "text". "text" will use the `TextTimeEmbedding` layer. + addition_time_embed_dim: (`int`, *optional*, defaults to `None`): + Dimension for the timestep embeddings. + num_class_embeds (`int`, *optional*, defaults to `None`): + Input dimension of the learnable embedding matrix to be projected to `time_embed_dim`, when performing + class conditioning with `class_embed_type` equal to `None`. + time_embedding_type (`str`, *optional*, defaults to `positional`): + The type of position embedding to use for timesteps. Choose from `positional` or `fourier`. + time_embedding_dim (`int`, *optional*, defaults to `None`): + An optional override for the dimension of the projected time embedding. + time_embedding_act_fn (`str`, *optional*, defaults to `None`): + Optional activation function to use only once on the time embeddings before they are passed to the rest of + the UNet. Choose from `silu`, `mish`, `gelu`, and `swish`. + timestep_post_act (`str`, *optional*, defaults to `None`): + The second activation function to use in timestep embedding. Choose from `silu`, `mish` and `gelu`. + time_cond_proj_dim (`int`, *optional*, defaults to `None`): + The dimension of `cond_proj` layer in the timestep embedding. + conv_in_kernel (`int`, *optional*, default to `3`): The kernel size of `conv_in` layer. conv_out_kernel (`int`, + *optional*, default to `3`): The kernel size of `conv_out` layer. projection_class_embeddings_input_dim (`int`, + *optional*): The dimension of the `class_labels` input when + `class_embed_type="projection"`. Required when `class_embed_type="projection"`. + class_embeddings_concat (`bool`, *optional*, defaults to `False`): Whether to concatenate the time + embeddings with the class embeddings. + mid_block_only_cross_attention (`bool`, *optional*, defaults to `None`): + Whether to use cross attention with the mid block when using the `UNetMidBlock2DSimpleCrossAttn`. If + `only_cross_attention` is given as a single boolean and `mid_block_only_cross_attention` is `None`, the + `only_cross_attention` value is used as the value for `mid_block_only_cross_attention`. Default to `False` + otherwise. + """ + + _supports_gradient_checkpointing = True + + @register_to_config + def __init__( + self, + sample_size: Optional[int] = None, + in_channels: int = 4, + out_channels: int = 4, + center_input_sample: bool = False, + flip_sin_to_cos: bool = True, + freq_shift: int = 0, + down_block_types: Tuple[str] = ( + "CrossAttnDownBlock2D", + "CrossAttnDownBlock2D", + "CrossAttnDownBlock2D", + "DownBlock2D", + ), + mid_block_type: Optional[str] = "UNetMidBlock2DCrossAttn", + up_block_types: Tuple[str] = ("UpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D"), + only_cross_attention: Union[bool, Tuple[bool]] = False, + block_out_channels: Tuple[int] = (320, 640, 1280, 1280), + layers_per_block: Union[int, Tuple[int]] = 2, + downsample_padding: int = 1, + mid_block_scale_factor: float = 1, + dropout: float = 0.0, + act_fn: str = "silu", + norm_num_groups: Optional[int] = 32, + norm_eps: float = 1e-5, + cross_attention_dim: Union[int, Tuple[int]] = 1280, + transformer_layers_per_block: Union[int, Tuple[int], Tuple[Tuple]] = 1, + reverse_transformer_layers_per_block: Optional[Tuple[Tuple[int]]] = None, + encoder_hid_dim: Optional[int] = None, + encoder_hid_dim_type: Optional[str] = None, + attention_head_dim: Union[int, Tuple[int]] = 8, + num_attention_heads: Optional[Union[int, Tuple[int]]] = None, + dual_cross_attention: bool = False, + use_linear_projection: bool = False, + class_embed_type: Optional[str] = None, + addition_embed_type: Optional[str] = None, + addition_time_embed_dim: Optional[int] = None, + num_class_embeds: Optional[int] = None, + upcast_attention: bool = False, + resnet_time_scale_shift: str = "default", + resnet_skip_time_act: bool = False, + resnet_out_scale_factor: int = 1.0, + time_embedding_type: str = "positional", + time_embedding_dim: Optional[int] = None, + time_embedding_act_fn: Optional[str] = None, + timestep_post_act: Optional[str] = None, + time_cond_proj_dim: Optional[int] = None, + conv_in_kernel: int = 3, + conv_out_kernel: int = 3, + projection_class_embeddings_input_dim: Optional[int] = None, + attention_type: str = "default", + class_embeddings_concat: bool = False, + mid_block_only_cross_attention: Optional[bool] = None, + cross_attention_norm: Optional[str] = None, + addition_embed_type_num_heads=64, + + # param for joint + video_feature_dim: tuple=(320, 640, 1280, 1280), + video_cross_attn_dim: int=1024, + video_frame_nums: int=16, + ): + super().__init__() + + self.sample_size = sample_size + + if num_attention_heads is not None: + raise ValueError( + "At the moment it is not possible to define the number of attention heads via `num_attention_heads` because of a naming issue as described in https://github.com/huggingface/diffusers/issues/2011#issuecomment-1547958131. Passing `num_attention_heads` will only be supported in diffusers v0.19." + ) + + # If `num_attention_heads` is not defined (which is the case for most models) + # it will default to `attention_head_dim`. This looks weird upon first reading it and it is. + # The reason for this behavior is to correct for incorrectly named variables that were introduced + # when this library was created. The incorrect naming was only discovered much later in https://github.com/huggingface/diffusers/issues/2011#issuecomment-1547958131 + # Changing `attention_head_dim` to `num_attention_heads` for 40,000+ configurations is too backwards breaking + # which is why we correct for the naming here. + num_attention_heads = num_attention_heads or attention_head_dim + + # Check inputs + if len(down_block_types) != len(up_block_types): + raise ValueError( + f"Must provide the same number of `down_block_types` as `up_block_types`. `down_block_types`: {down_block_types}. `up_block_types`: {up_block_types}." + ) + + if len(block_out_channels) != len(down_block_types): + raise ValueError( + f"Must provide the same number of `block_out_channels` as `down_block_types`. `block_out_channels`: {block_out_channels}. `down_block_types`: {down_block_types}." + ) + + if not isinstance(only_cross_attention, bool) and len(only_cross_attention) != len(down_block_types): + raise ValueError( + f"Must provide the same number of `only_cross_attention` as `down_block_types`. `only_cross_attention`: {only_cross_attention}. `down_block_types`: {down_block_types}." + ) + + if not isinstance(num_attention_heads, int) and len(num_attention_heads) != len(down_block_types): + raise ValueError( + f"Must provide the same number of `num_attention_heads` as `down_block_types`. `num_attention_heads`: {num_attention_heads}. `down_block_types`: {down_block_types}." + ) + + if not isinstance(attention_head_dim, int) and len(attention_head_dim) != len(down_block_types): + raise ValueError( + f"Must provide the same number of `attention_head_dim` as `down_block_types`. `attention_head_dim`: {attention_head_dim}. `down_block_types`: {down_block_types}." + ) + + if isinstance(cross_attention_dim, list) and len(cross_attention_dim) != len(down_block_types): + raise ValueError( + f"Must provide the same number of `cross_attention_dim` as `down_block_types`. `cross_attention_dim`: {cross_attention_dim}. `down_block_types`: {down_block_types}." + ) + + if not isinstance(layers_per_block, int) and len(layers_per_block) != len(down_block_types): + raise ValueError( + f"Must provide the same number of `layers_per_block` as `down_block_types`. `layers_per_block`: {layers_per_block}. `down_block_types`: {down_block_types}." + ) + if isinstance(transformer_layers_per_block, list) and reverse_transformer_layers_per_block is None: + for layer_number_per_block in transformer_layers_per_block: + if isinstance(layer_number_per_block, list): + raise ValueError("Must provide 'reverse_transformer_layers_per_block` if using asymmetrical UNet.") + + # input + conv_in_padding = (conv_in_kernel - 1) // 2 + self.conv_in = nn.Conv2d( + in_channels, block_out_channels[0], kernel_size=conv_in_kernel, padding=conv_in_padding + ) + + # time + if time_embedding_type == "fourier": + time_embed_dim = time_embedding_dim or block_out_channels[0] * 2 + if time_embed_dim % 2 != 0: + raise ValueError(f"`time_embed_dim` should be divisible by 2, but is {time_embed_dim}.") + self.time_proj = GaussianFourierProjection( + time_embed_dim // 2, set_W_to_weight=False, log=False, flip_sin_to_cos=flip_sin_to_cos + ) + timestep_input_dim = time_embed_dim + elif time_embedding_type == "positional": + time_embed_dim = time_embedding_dim or block_out_channels[0] * 4 + + self.time_proj = Timesteps(block_out_channels[0], flip_sin_to_cos, freq_shift) + timestep_input_dim = block_out_channels[0] + else: + raise ValueError( + f"{time_embedding_type} does not exist. Please make sure to use one of `fourier` or `positional`." + ) + + self.time_embedding = TimestepEmbedding( + timestep_input_dim, + time_embed_dim, + act_fn=act_fn, + post_act_fn=timestep_post_act, + cond_proj_dim=time_cond_proj_dim, + ) + + if encoder_hid_dim_type is None and encoder_hid_dim is not None: + encoder_hid_dim_type = "text_proj" + self.register_to_config(encoder_hid_dim_type=encoder_hid_dim_type) + logger.info("encoder_hid_dim_type defaults to 'text_proj' as `encoder_hid_dim` is defined.") + + if encoder_hid_dim is None and encoder_hid_dim_type is not None: + raise ValueError( + f"`encoder_hid_dim` has to be defined when `encoder_hid_dim_type` is set to {encoder_hid_dim_type}." + ) + + if encoder_hid_dim_type == "text_proj": + self.encoder_hid_proj = nn.Linear(encoder_hid_dim, cross_attention_dim) + elif encoder_hid_dim_type == "text_image_proj": + # image_embed_dim DOESN'T have to be `cross_attention_dim`. To not clutter the __init__ too much + # they are set to `cross_attention_dim` here as this is exactly the required dimension for the currently only use + # case when `addition_embed_type == "text_image_proj"` (Kadinsky 2.1)` + self.encoder_hid_proj = TextImageProjection( + text_embed_dim=encoder_hid_dim, + image_embed_dim=cross_attention_dim, + cross_attention_dim=cross_attention_dim, + ) + elif encoder_hid_dim_type == "image_proj": + # Kandinsky 2.2 + self.encoder_hid_proj = ImageProjection( + image_embed_dim=encoder_hid_dim, + cross_attention_dim=cross_attention_dim, + ) + elif encoder_hid_dim_type is not None: + raise ValueError( + f"encoder_hid_dim_type: {encoder_hid_dim_type} must be None, 'text_proj' or 'text_image_proj'." + ) + else: + self.encoder_hid_proj = None + + # class embedding + if class_embed_type is None and num_class_embeds is not None: + self.class_embedding = nn.Embedding(num_class_embeds, time_embed_dim) + elif class_embed_type == "timestep": + self.class_embedding = TimestepEmbedding(timestep_input_dim, time_embed_dim, act_fn=act_fn) + elif class_embed_type == "identity": + self.class_embedding = nn.Identity(time_embed_dim, time_embed_dim) + elif class_embed_type == "projection": + if projection_class_embeddings_input_dim is None: + raise ValueError( + "`class_embed_type`: 'projection' requires `projection_class_embeddings_input_dim` be set" + ) + # The projection `class_embed_type` is the same as the timestep `class_embed_type` except + # 1. the `class_labels` inputs are not first converted to sinusoidal embeddings + # 2. it projects from an arbitrary input dimension. + # + # Note that `TimestepEmbedding` is quite general, being mainly linear layers and activations. + # When used for embedding actual timesteps, the timesteps are first converted to sinusoidal embeddings. + # As a result, `TimestepEmbedding` can be passed arbitrary vectors. + self.class_embedding = TimestepEmbedding(projection_class_embeddings_input_dim, time_embed_dim) + elif class_embed_type == "simple_projection": + if projection_class_embeddings_input_dim is None: + raise ValueError( + "`class_embed_type`: 'simple_projection' requires `projection_class_embeddings_input_dim` be set" + ) + self.class_embedding = nn.Linear(projection_class_embeddings_input_dim, time_embed_dim) + else: + self.class_embedding = None + + if addition_embed_type == "text": + if encoder_hid_dim is not None: + text_time_embedding_from_dim = encoder_hid_dim + else: + text_time_embedding_from_dim = cross_attention_dim + + self.add_embedding = TextTimeEmbedding( + text_time_embedding_from_dim, time_embed_dim, num_heads=addition_embed_type_num_heads + ) + elif addition_embed_type == "text_image": + # text_embed_dim and image_embed_dim DON'T have to be `cross_attention_dim`. To not clutter the __init__ too much + # they are set to `cross_attention_dim` here as this is exactly the required dimension for the currently only use + # case when `addition_embed_type == "text_image"` (Kadinsky 2.1)` + self.add_embedding = TextImageTimeEmbedding( + text_embed_dim=cross_attention_dim, image_embed_dim=cross_attention_dim, time_embed_dim=time_embed_dim + ) + elif addition_embed_type == "text_time": + self.add_time_proj = Timesteps(addition_time_embed_dim, flip_sin_to_cos, freq_shift) + self.add_embedding = TimestepEmbedding(projection_class_embeddings_input_dim, time_embed_dim) + elif addition_embed_type == "image": + # Kandinsky 2.2 + self.add_embedding = ImageTimeEmbedding(image_embed_dim=encoder_hid_dim, time_embed_dim=time_embed_dim) + elif addition_embed_type == "image_hint": + # Kandinsky 2.2 ControlNet + self.add_embedding = ImageHintTimeEmbedding(image_embed_dim=encoder_hid_dim, time_embed_dim=time_embed_dim) + elif addition_embed_type is not None: + raise ValueError(f"addition_embed_type: {addition_embed_type} must be None, 'text' or 'text_image'.") + + if time_embedding_act_fn is None: + self.time_embed_act = None + else: + self.time_embed_act = get_activation(time_embedding_act_fn) + + self.down_blocks = nn.ModuleList([]) + self.up_blocks = nn.ModuleList([]) + + if isinstance(only_cross_attention, bool): + if mid_block_only_cross_attention is None: + mid_block_only_cross_attention = only_cross_attention + + only_cross_attention = [only_cross_attention] * len(down_block_types) + + if mid_block_only_cross_attention is None: + mid_block_only_cross_attention = False + + if isinstance(num_attention_heads, int): + num_attention_heads = (num_attention_heads,) * len(down_block_types) + + if isinstance(attention_head_dim, int): + attention_head_dim = (attention_head_dim,) * len(down_block_types) + + if isinstance(cross_attention_dim, int): + cross_attention_dim = (cross_attention_dim,) * len(down_block_types) + + if isinstance(layers_per_block, int): + layers_per_block = [layers_per_block] * len(down_block_types) + + if isinstance(transformer_layers_per_block, int): + transformer_layers_per_block = [transformer_layers_per_block] * len(down_block_types) + + if class_embeddings_concat: + # The time embeddings are concatenated with the class embeddings. The dimension of the + # time embeddings passed to the down, middle, and up blocks is twice the dimension of the + # regular time embeddings + blocks_time_embed_dim = time_embed_dim * 2 + else: + blocks_time_embed_dim = time_embed_dim + + # down + output_channel = block_out_channels[0] + for i, down_block_type in enumerate(down_block_types): + input_channel = output_channel + output_channel = block_out_channels[i] + is_final_block = i == len(block_out_channels) - 1 + + down_block = get_down_block( + down_block_type, + num_layers=layers_per_block[i], + transformer_layers_per_block=transformer_layers_per_block[i], + in_channels=input_channel, + out_channels=output_channel, + temb_channels=blocks_time_embed_dim, + add_downsample=not is_final_block, + resnet_eps=norm_eps, + resnet_act_fn=act_fn, + resnet_groups=norm_num_groups, + cross_attention_dim=cross_attention_dim[i], + num_attention_heads=num_attention_heads[i], + downsample_padding=downsample_padding, + dual_cross_attention=dual_cross_attention, + use_linear_projection=use_linear_projection, + only_cross_attention=only_cross_attention[i], + upcast_attention=upcast_attention, + resnet_time_scale_shift=resnet_time_scale_shift, + attention_type=attention_type, + resnet_skip_time_act=resnet_skip_time_act, + resnet_out_scale_factor=resnet_out_scale_factor, + cross_attention_norm=cross_attention_norm, + attention_head_dim=attention_head_dim[i] if attention_head_dim[i] is not None else output_channel, + dropout=dropout, + ) + self.down_blocks.append(down_block) + + # mid + if mid_block_type == "UNetMidBlock2DCrossAttn": + self.mid_block = UNetMidBlock2DCrossAttn( + transformer_layers_per_block=transformer_layers_per_block[-1], + in_channels=block_out_channels[-1], + temb_channels=blocks_time_embed_dim, + dropout=dropout, + resnet_eps=norm_eps, + resnet_act_fn=act_fn, + output_scale_factor=mid_block_scale_factor, + resnet_time_scale_shift=resnet_time_scale_shift, + cross_attention_dim=cross_attention_dim[-1], + num_attention_heads=num_attention_heads[-1], + resnet_groups=norm_num_groups, + dual_cross_attention=dual_cross_attention, + use_linear_projection=use_linear_projection, + upcast_attention=upcast_attention, + attention_type=attention_type, + ) + elif mid_block_type == "UNetMidBlock2DSimpleCrossAttn": + self.mid_block = UNetMidBlock2DSimpleCrossAttn( + in_channels=block_out_channels[-1], + temb_channels=blocks_time_embed_dim, + dropout=dropout, + resnet_eps=norm_eps, + resnet_act_fn=act_fn, + output_scale_factor=mid_block_scale_factor, + cross_attention_dim=cross_attention_dim[-1], + attention_head_dim=attention_head_dim[-1], + resnet_groups=norm_num_groups, + resnet_time_scale_shift=resnet_time_scale_shift, + skip_time_act=resnet_skip_time_act, + only_cross_attention=mid_block_only_cross_attention, + cross_attention_norm=cross_attention_norm, + ) + elif mid_block_type == "UNetMidBlock2D": + self.mid_block = UNetMidBlock2D( + in_channels=block_out_channels[-1], + temb_channels=blocks_time_embed_dim, + dropout=dropout, + num_layers=0, + resnet_eps=norm_eps, + resnet_act_fn=act_fn, + output_scale_factor=mid_block_scale_factor, + resnet_groups=norm_num_groups, + resnet_time_scale_shift=resnet_time_scale_shift, + add_attention=False, + ) + elif mid_block_type is None: + self.mid_block = None + else: + raise ValueError(f"unknown mid_block_type : {mid_block_type}") + + # count how many layers upsample the images + self.num_upsamplers = 0 + + # up + reversed_block_out_channels = list(reversed(block_out_channels)) + reversed_num_attention_heads = list(reversed(num_attention_heads)) + reversed_layers_per_block = list(reversed(layers_per_block)) + reversed_cross_attention_dim = list(reversed(cross_attention_dim)) + reversed_transformer_layers_per_block = ( + list(reversed(transformer_layers_per_block)) + if reverse_transformer_layers_per_block is None + else reverse_transformer_layers_per_block + ) + only_cross_attention = list(reversed(only_cross_attention)) + + output_channel = reversed_block_out_channels[0] + for i, up_block_type in enumerate(up_block_types): + is_final_block = i == len(block_out_channels) - 1 + + prev_output_channel = output_channel + output_channel = reversed_block_out_channels[i] + input_channel = reversed_block_out_channels[min(i + 1, len(block_out_channels) - 1)] + + # add upsample block for all BUT final layer + if not is_final_block: + add_upsample = True + self.num_upsamplers += 1 + else: + add_upsample = False + + up_block = get_up_block( + up_block_type, + num_layers=reversed_layers_per_block[i] + 1, + transformer_layers_per_block=reversed_transformer_layers_per_block[i], + in_channels=input_channel, + out_channels=output_channel, + prev_output_channel=prev_output_channel, + temb_channels=blocks_time_embed_dim, + add_upsample=add_upsample, + resnet_eps=norm_eps, + resnet_act_fn=act_fn, + resolution_idx=i, + resnet_groups=norm_num_groups, + cross_attention_dim=reversed_cross_attention_dim[i], + num_attention_heads=reversed_num_attention_heads[i], + dual_cross_attention=dual_cross_attention, + use_linear_projection=use_linear_projection, + only_cross_attention=only_cross_attention[i], + upcast_attention=upcast_attention, + resnet_time_scale_shift=resnet_time_scale_shift, + attention_type=attention_type, + resnet_skip_time_act=resnet_skip_time_act, + resnet_out_scale_factor=resnet_out_scale_factor, + cross_attention_norm=cross_attention_norm, + attention_head_dim=attention_head_dim[i] if attention_head_dim[i] is not None else output_channel, + dropout=dropout, + ) + self.up_blocks.append(up_block) + prev_output_channel = output_channel + + # out + if norm_num_groups is not None: + self.conv_norm_out = nn.GroupNorm( + num_channels=block_out_channels[0], num_groups=norm_num_groups, eps=norm_eps + ) + + self.conv_act = get_activation(act_fn) + + else: + self.conv_norm_out = None + self.conv_act = None + + conv_out_padding = (conv_out_kernel - 1) // 2 + self.conv_out = nn.Conv2d( + block_out_channels[0], out_channels, kernel_size=conv_out_kernel, padding=conv_out_padding + ) + + if attention_type in ["gated", "gated-text-image"]: + positive_len = 768 + if isinstance(cross_attention_dim, int): + positive_len = cross_attention_dim + elif isinstance(cross_attention_dim, tuple) or isinstance(cross_attention_dim, list): + positive_len = cross_attention_dim[0] + + feature_type = "text-only" if attention_type == "gated" else "text-image" + self.position_net = TimeProjModel( + positive_len=positive_len, out_dim=cross_attention_dim, feature_type=feature_type + ) + + # additional settings + self.video_feature_dim = video_feature_dim + self.cross_attention_dim = cross_attention_dim + self.video_cross_attn_dim = video_cross_attn_dim + self.video_frame_nums = video_frame_nums + + self.multi_frames_condition = False + + def load_attention(self): + attn_dict = {} + for name in self.attn_processors.keys(): + # if self-attention, save feature + if name.endswith("attn1.processor"): + if is_xformers_available(): + attn_dict[name] = XFormersAttnProcessor() + else: + attn_dict[name] = AttnProcessor() + else: + attn_dict[name] = AttnProcessor2_0() + self.set_attn_processor(attn_dict) + + def get_writer_feature(self): + return self.attn_feature_writer.get_cross_attention_feature() + + def clear_writer_feature(self): + self.attn_feature_writer.clear_cross_attention_feature() + + def disable_feature_adapters(self): + raise NotImplementedError + + def set_reader_feature(self, features:list): + return self.attn_feature_reader.set_cross_attention_feature(features) + + @property + def attn_processors(self) -> Dict[str, AttentionProcessor]: + r""" + Returns: + `dict` of attention processors: A dictionary containing all attention processors used in the model with + indexed by its weight name. + """ + # set recursively + processors = {} + + def fn_recursive_add_processors(name: str, module: torch.nn.Module, processors: Dict[str, AttentionProcessor]): + if hasattr(module, "get_processor"): + processors[f"{name}.processor"] = module.get_processor(return_deprecated_lora=True) + + for sub_name, child in module.named_children(): + fn_recursive_add_processors(f"{name}.{sub_name}", child, processors) + + return processors + + for name, module in self.named_children(): + fn_recursive_add_processors(name, module, processors) + + return processors + + def set_attn_processor( + self, processor: Union[AttentionProcessor, Dict[str, AttentionProcessor]], _remove_lora=False + ): + r""" + Sets the attention processor to use to compute attention. + + Parameters: + processor (`dict` of `AttentionProcessor` or only `AttentionProcessor`): + The instantiated processor class or a dictionary of processor classes that will be set as the processor + for **all** `Attention` layers. + + If `processor` is a dict, the key needs to define the path to the corresponding cross attention + processor. This is strongly recommended when setting trainable attention processors. + + """ + count = len(self.attn_processors.keys()) + + if isinstance(processor, dict) and len(processor) != count: + raise ValueError( + f"A dict of processors was passed, but the number of processors {len(processor)} does not match the" + f" number of attention layers: {count}. Please make sure to pass {count} processor classes." + ) + + def fn_recursive_attn_processor(name: str, module: torch.nn.Module, processor): + if hasattr(module, "set_processor"): + if not isinstance(processor, dict): + module.set_processor(processor, _remove_lora=_remove_lora) + else: + module.set_processor(processor.pop(f"{name}.processor"), _remove_lora=_remove_lora) + + for sub_name, child in module.named_children(): + fn_recursive_attn_processor(f"{name}.{sub_name}", child, processor) + + for name, module in self.named_children(): + fn_recursive_attn_processor(name, module, processor) + + def set_default_attn_processor(self): + """ + Disables custom attention processors and sets the default attention implementation. + """ + if all(proc.__class__ in ADDED_KV_ATTENTION_PROCESSORS for proc in self.attn_processors.values()): + processor = AttnAddedKVProcessor() + elif all(proc.__class__ in CROSS_ATTENTION_PROCESSORS for proc in self.attn_processors.values()): + processor = AttnProcessor() + else: + raise ValueError( + f"Cannot call `set_default_attn_processor` when attention processors are of type {next(iter(self.attn_processors.values()))}" + ) + + self.set_attn_processor(processor, _remove_lora=True) + + def set_attention_slice(self, slice_size): + r""" + Enable sliced attention computation. + + When this option is enabled, the attention module splits the input tensor in slices to compute attention in + several steps. This is useful for saving some memory in exchange for a small decrease in speed. + + Args: + slice_size (`str` or `int` or `list(int)`, *optional*, defaults to `"auto"`): + When `"auto"`, input to the attention heads is halved, so attention is computed in two steps. If + `"max"`, maximum amount of memory is saved by running only one slice at a time. If a number is + provided, uses as many slices as `attention_head_dim // slice_size`. In this case, `attention_head_dim` + must be a multiple of `slice_size`. + """ + sliceable_head_dims = [] + + def fn_recursive_retrieve_sliceable_dims(module: torch.nn.Module): + if hasattr(module, "set_attention_slice"): + sliceable_head_dims.append(module.sliceable_head_dim) + + for child in module.children(): + fn_recursive_retrieve_sliceable_dims(child) + + # retrieve number of attention layers + for module in self.children(): + fn_recursive_retrieve_sliceable_dims(module) + + num_sliceable_layers = len(sliceable_head_dims) + + if slice_size == "auto": + # half the attention head size is usually a good trade-off between + # speed and memory + slice_size = [dim // 2 for dim in sliceable_head_dims] + elif slice_size == "max": + # make smallest slice possible + slice_size = num_sliceable_layers * [1] + + slice_size = num_sliceable_layers * [slice_size] if not isinstance(slice_size, list) else slice_size + + if len(slice_size) != len(sliceable_head_dims): + raise ValueError( + f"You have provided {len(slice_size)}, but {self.config} has {len(sliceable_head_dims)} different" + f" attention layers. Make sure to match `len(slice_size)` to be {len(sliceable_head_dims)}." + ) + + for i in range(len(slice_size)): + size = slice_size[i] + dim = sliceable_head_dims[i] + if size is not None and size > dim: + raise ValueError(f"size {size} has to be smaller or equal to {dim}.") + + # Recursively walk through all the children. + # Any children which exposes the set_attention_slice method + # gets the message + def fn_recursive_set_attention_slice(module: torch.nn.Module, slice_size: List[int]): + if hasattr(module, "set_attention_slice"): + module.set_attention_slice(slice_size.pop()) + + for child in module.children(): + fn_recursive_set_attention_slice(child, slice_size) + + reversed_slice_size = list(reversed(slice_size)) + for module in self.children(): + fn_recursive_set_attention_slice(module, reversed_slice_size) + + def _set_gradient_checkpointing(self, module, value=False): + if hasattr(module, "gradient_checkpointing"): + module.gradient_checkpointing = value + + def enable_freeu(self, s1, s2, b1, b2): + r"""Enables the FreeU mechanism from https://arxiv.org/abs/2309.11497. + + The suffixes after the scaling factors represent the stage blocks where they are being applied. + + Please refer to the [official repository](https://github.com/ChenyangSi/FreeU) for combinations of values that + are known to work well for different pipelines such as Stable Diffusion v1, v2, and Stable Diffusion XL. + + Args: + s1 (`float`): + Scaling factor for stage 1 to attenuate the contributions of the skip features. This is done to + mitigate the "oversmoothing effect" in the enhanced denoising process. + s2 (`float`): + Scaling factor for stage 2 to attenuate the contributions of the skip features. This is done to + mitigate the "oversmoothing effect" in the enhanced denoising process. + b1 (`float`): Scaling factor for stage 1 to amplify the contributions of backbone features. + b2 (`float`): Scaling factor for stage 2 to amplify the contributions of backbone features. + """ + for i, upsample_block in enumerate(self.up_blocks): + setattr(upsample_block, "s1", s1) + setattr(upsample_block, "s2", s2) + setattr(upsample_block, "b1", b1) + setattr(upsample_block, "b2", b2) + + def disable_freeu(self): + """Disables the FreeU mechanism.""" + freeu_keys = {"s1", "s2", "b1", "b2"} + for i, upsample_block in enumerate(self.up_blocks): + for k in freeu_keys: + if hasattr(upsample_block, k) or getattr(upsample_block, k, None) is not None: + setattr(upsample_block, k, None) + + def fuse_qkv_projections(self): + """ + Enables fused QKV projections. For self-attention modules, all projection matrices (i.e., query, + key, value) are fused. For cross-attention modules, key and value projection matrices are fused. + + + + This API is 🧪 experimental. + + + """ + self.original_attn_processors = None + + for _, attn_processor in self.attn_processors.items(): + if "Added" in str(attn_processor.__class__.__name__): + raise ValueError("`fuse_qkv_projections()` is not supported for models having added KV projections.") + + self.original_attn_processors = self.attn_processors + + for module in self.modules(): + if isinstance(module, Attention): + module.fuse_projections(fuse=True) + + def unfuse_qkv_projections(self): + """Disables the fused QKV projection if enabled. + + + + This API is 🧪 experimental. + + + + """ + if self.original_attn_processors is not None: + self.set_attn_processor(self.original_attn_processors) + + def forward( + self, + sample: torch.FloatTensor, + timestep: Union[torch.Tensor, float, int], + encoder_hidden_states: torch.Tensor, + class_labels: Optional[torch.Tensor] = None, + timestep_cond: Optional[torch.Tensor] = None, + attention_mask: Optional[torch.Tensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + added_cond_kwargs: Optional[Dict[str, torch.Tensor]] = None, + down_block_additional_residuals: Optional[Tuple[torch.Tensor]] = None, + mid_block_additional_residual: Optional[torch.Tensor] = None, + down_intrablock_additional_residuals: Optional[Tuple[torch.Tensor]] = None, + encoder_attention_mask: Optional[torch.Tensor] = None, + return_dict: bool = True, + ) -> Union[UNet2DConditionOutput, Tuple]: + # import ipdb; ipdb.set_trace() + r""" + The [`UNet2DConditionModel`] forward method. + + Args: + sample (`torch.FloatTensor`): + The noisy input tensor with the following shape `(batch, channel, height, width)`. + timestep (`torch.FloatTensor` or `float` or `int`): The number of timesteps to denoise an input. + encoder_hidden_states (`torch.FloatTensor`): + The encoder hidden states with shape `(batch, sequence_length, feature_dim)`. + class_labels (`torch.Tensor`, *optional*, defaults to `None`): + Optional class labels for conditioning. Their embeddings will be summed with the timestep embeddings. + timestep_cond: (`torch.Tensor`, *optional*, defaults to `None`): + Conditional embeddings for timestep. If provided, the embeddings will be summed with the samples passed + through the `self.time_embedding` layer to obtain the timestep embeddings. + attention_mask (`torch.Tensor`, *optional*, defaults to `None`): + An attention mask of shape `(batch, key_tokens)` is applied to `encoder_hidden_states`. If `1` the mask + is kept, otherwise if `0` it is discarded. Mask will be converted into a bias, which adds large + negative values to the attention scores corresponding to "discard" tokens. + cross_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under + `self.processor` in + [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + added_cond_kwargs: (`dict`, *optional*): + A kwargs dictionary containing additional embeddings that if specified are added to the embeddings that + are passed along to the UNet blocks. + down_block_additional_residuals: (`tuple` of `torch.Tensor`, *optional*): + A tuple of tensors that if specified are added to the residuals of down unet blocks. + mid_block_additional_residual: (`torch.Tensor`, *optional*): + A tensor that if specified is added to the residual of the middle unet block. + encoder_attention_mask (`torch.Tensor`): + A cross-attention mask of shape `(batch, sequence_length)` is applied to `encoder_hidden_states`. If + `True` the mask is kept, otherwise if `False` it is discarded. Mask will be converted into a bias, + which adds large negative values to the attention scores corresponding to "discard" tokens. + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~models.unet_2d_condition.UNet2DConditionOutput`] instead of a plain + tuple. + cross_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the [`AttnProcessor`]. + added_cond_kwargs: (`dict`, *optional*): + A kwargs dictionary containin additional embeddings that if specified are added to the embeddings that + are passed along to the UNet blocks. + down_block_additional_residuals (`tuple` of `torch.Tensor`, *optional*): + additional residuals to be added to UNet long skip connections from down blocks to up blocks for + example from ControlNet side model(s) + mid_block_additional_residual (`torch.Tensor`, *optional*): + additional residual to be added to UNet mid block output, for example from ControlNet side model + down_intrablock_additional_residuals (`tuple` of `torch.Tensor`, *optional*): + additional residuals to be added within UNet down blocks, for example from T2I-Adapter side model(s) + + Returns: + [`~models.unet_2d_condition.UNet2DConditionOutput`] or `tuple`: + If `return_dict` is True, an [`~models.unet_2d_condition.UNet2DConditionOutput`] is returned, otherwise + a `tuple` is returned where the first element is the sample tensor. + """ + # By default samples have to be AT least a multiple of the overall upsampling factor. + # The overall upsampling factor is equal to 2 ** (# num of upsampling layers). + # However, the upsampling interpolation output size can be forced to fit any upsampling size + # on the fly if necessary. + default_overall_up_factor = 2**self.num_upsamplers + + # upsample size should be forwarded when sample is not a multiple of `default_overall_up_factor` + forward_upsample_size = False + upsample_size = None + + for dim in sample.shape[-2:]: + if dim % default_overall_up_factor != 0: + # Forward upsample size to force interpolation output size. + forward_upsample_size = True + break + + # ensure attention_mask is a bias, and give it a singleton query_tokens dimension + # expects mask of shape: + # [batch, key_tokens] + # adds singleton query_tokens dimension: + # [batch, 1, key_tokens] + # this helps to broadcast it as a bias over attention scores, which will be in one of the following shapes: + # [batch, heads, query_tokens, key_tokens] (e.g. torch sdp attn) + # [batch * heads, query_tokens, key_tokens] (e.g. xformers or classic attn) + if attention_mask is not None: + # assume that mask is expressed as: + # (1 = keep, 0 = discard) + # convert mask into a bias that can be added to attention scores: + # (keep = +0, discard = -10000.0) + attention_mask = (1 - attention_mask.to(sample.dtype)) * -10000.0 + attention_mask = attention_mask.unsqueeze(1) + + # convert encoder_attention_mask to a bias the same way we do for attention_mask + if encoder_attention_mask is not None: + encoder_attention_mask = (1 - encoder_attention_mask.to(sample.dtype)) * -10000.0 + encoder_attention_mask = encoder_attention_mask.unsqueeze(1) + + # 0. center input if necessary + if self.config.center_input_sample: + sample = 2 * sample - 1.0 + + # 1. time + timesteps = timestep + if not torch.is_tensor(timesteps): + # TODO: this requires sync between CPU and GPU. So try to pass timesteps as tensors if you can + # This would be a good case for the `match` statement (Python 3.10+) + is_mps = sample.device.type == "mps" + if isinstance(timestep, float): + dtype = torch.float32 if is_mps else torch.float64 + else: + dtype = torch.int32 if is_mps else torch.int64 + timesteps = torch.tensor([timesteps], dtype=dtype, device=sample.device) + elif len(timesteps.shape) == 0: + timesteps = timesteps[None].to(sample.device) + + # broadcast to batch dimension in a way that's compatible with ONNX/Core ML + timesteps = timesteps.expand(sample.shape[0]) + + t_emb = self.time_proj(timesteps) + + # `Timesteps` does not contain any weights and will always return f32 tensors + # but time_embedding might actually be running in fp16. so we need to cast here. + # there might be better ways to encapsulate this. + t_emb = t_emb.to(dtype=sample.dtype) + + emb = self.time_embedding(t_emb, timestep_cond) + aug_emb = None + + if self.class_embedding is not None: + if class_labels is None: + raise ValueError("class_labels should be provided when num_class_embeds > 0") + + if self.config.class_embed_type == "timestep": + class_labels = self.time_proj(class_labels) + + # `Timesteps` does not contain any weights and will always return f32 tensors + # there might be better ways to encapsulate this. + class_labels = class_labels.to(dtype=sample.dtype) + + class_emb = self.class_embedding(class_labels).to(dtype=sample.dtype) + + if self.config.class_embeddings_concat: + emb = torch.cat([emb, class_emb], dim=-1) + else: + emb = emb + class_emb + + if self.config.addition_embed_type == "text": + aug_emb = self.add_embedding(encoder_hidden_states) + elif self.config.addition_embed_type == "text_image": + # Kandinsky 2.1 - style + if "image_embeds" not in added_cond_kwargs: + raise ValueError( + f"{self.__class__} has the config param `addition_embed_type` set to 'text_image' which requires the keyword argument `image_embeds` to be passed in `added_cond_kwargs`" + ) + + image_embs = added_cond_kwargs.get("image_embeds") + text_embs = added_cond_kwargs.get("text_embeds", encoder_hidden_states) + aug_emb = self.add_embedding(text_embs, image_embs) + elif self.config.addition_embed_type == "text_time": + # SDXL - style + if "text_embeds" not in added_cond_kwargs: + raise ValueError( + f"{self.__class__} has the config param `addition_embed_type` set to 'text_time' which requires the keyword argument `text_embeds` to be passed in `added_cond_kwargs`" + ) + text_embeds = added_cond_kwargs.get("text_embeds") + if "time_ids" not in added_cond_kwargs: + raise ValueError( + f"{self.__class__} has the config param `addition_embed_type` set to 'text_time' which requires the keyword argument `time_ids` to be passed in `added_cond_kwargs`" + ) + time_ids = added_cond_kwargs.get("time_ids") + time_embeds = self.add_time_proj(time_ids.flatten()) + time_embeds = time_embeds.reshape((text_embeds.shape[0], -1)) + add_embeds = torch.concat([text_embeds, time_embeds], dim=-1) + add_embeds = add_embeds.to(emb.dtype) + aug_emb = self.add_embedding(add_embeds) + elif self.config.addition_embed_type == "image": + # Kandinsky 2.2 - style + if "image_embeds" not in added_cond_kwargs: + raise ValueError( + f"{self.__class__} has the config param `addition_embed_type` set to 'image' which requires the keyword argument `image_embeds` to be passed in `added_cond_kwargs`" + ) + image_embs = added_cond_kwargs.get("image_embeds") + aug_emb = self.add_embedding(image_embs) + elif self.config.addition_embed_type == "image_hint": + # Kandinsky 2.2 - style + if "image_embeds" not in added_cond_kwargs or "hint" not in added_cond_kwargs: + raise ValueError( + f"{self.__class__} has the config param `addition_embed_type` set to 'image_hint' which requires the keyword arguments `image_embeds` and `hint` to be passed in `added_cond_kwargs`" + ) + image_embs = added_cond_kwargs.get("image_embeds") + hint = added_cond_kwargs.get("hint") + aug_emb, hint = self.add_embedding(image_embs, hint) + sample = torch.cat([sample, hint], dim=1) + + emb = emb + aug_emb if aug_emb is not None else emb + + if self.time_embed_act is not None: + emb = self.time_embed_act(emb) + + if self.encoder_hid_proj is not None and self.config.encoder_hid_dim_type == "text_proj": + encoder_hidden_states = self.encoder_hid_proj(encoder_hidden_states) + elif self.encoder_hid_proj is not None and self.config.encoder_hid_dim_type == "text_image_proj": + # Kadinsky 2.1 - style + if "image_embeds" not in added_cond_kwargs: + raise ValueError( + f"{self.__class__} has the config param `encoder_hid_dim_type` set to 'text_image_proj' which requires the keyword argument `image_embeds` to be passed in `added_conditions`" + ) + + image_embeds = added_cond_kwargs.get("image_embeds") + encoder_hidden_states = self.encoder_hid_proj(encoder_hidden_states, image_embeds) + elif self.encoder_hid_proj is not None and self.config.encoder_hid_dim_type == "image_proj": + # Kandinsky 2.2 - style + if "image_embeds" not in added_cond_kwargs: + raise ValueError( + f"{self.__class__} has the config param `encoder_hid_dim_type` set to 'image_proj' which requires the keyword argument `image_embeds` to be passed in `added_conditions`" + ) + image_embeds = added_cond_kwargs.get("image_embeds") + encoder_hidden_states = self.encoder_hid_proj(image_embeds) + elif self.encoder_hid_proj is not None and self.config.encoder_hid_dim_type == "ip_image_proj": + if "image_embeds" not in added_cond_kwargs: + raise ValueError( + f"{self.__class__} has the config param `encoder_hid_dim_type` set to 'ip_image_proj' which requires the keyword argument `image_embeds` to be passed in `added_conditions`" + ) + image_embeds = added_cond_kwargs.get("image_embeds") + image_embeds = self.encoder_hid_proj(image_embeds) + if isinstance(image_embeds, list): + image_embeds = [image_embed.to(encoder_hidden_states.dtype) for image_embed in image_embeds] + else: + image_embeds = image_embeds.to(encoder_hidden_states.dtype) + encoder_hidden_states = (encoder_hidden_states, image_embeds) + # encoder_hidden_states = torch.cat([encoder_hidden_states, image_embeds], dim=1) + # import ipdb; ipdb.set_trace() + # 2. pre-process + sample = self.conv_in(sample) + + # 2.5 GLIGEN position net + if cross_attention_kwargs is not None and cross_attention_kwargs.get("gligen", None) is not None: + cross_attention_kwargs = cross_attention_kwargs.copy() + gligen_args = cross_attention_kwargs.pop("gligen") + cross_attention_kwargs["gligen"] = {"objs": self.position_net(**gligen_args)} + + # 3. down + lora_scale = cross_attention_kwargs.get("scale", 1.0) if cross_attention_kwargs is not None else 1.0 + if USE_PEFT_BACKEND: + # weight the lora layers by setting `lora_scale` for each PEFT layer + scale_lora_layers(self, lora_scale) + + is_controlnet = mid_block_additional_residual is not None and down_block_additional_residuals is not None + # using new arg down_intrablock_additional_residuals for T2I-Adapters, to distinguish from controlnets + is_adapter = down_intrablock_additional_residuals is not None + # maintain backward compatibility for legacy usage, where + # T2I-Adapter and ControlNet both use down_block_additional_residuals arg + # but can only use one or the other + if not is_adapter and mid_block_additional_residual is None and down_block_additional_residuals is not None: + deprecate( + "T2I should not use down_block_additional_residuals", + "1.3.0", + "Passing intrablock residual connections with `down_block_additional_residuals` is deprecated \ + and will be removed in diffusers 1.3.0. `down_block_additional_residuals` should only be used \ + for ControlNet. Please make sure use `down_intrablock_additional_residuals` instead. ", + standard_warn=False, + ) + down_intrablock_additional_residuals = down_block_additional_residuals + is_adapter = True + # import ipdb; ipdb.set_trace() + down_block_res_samples = (sample,) + for downsample_block in self.down_blocks: + if hasattr(downsample_block, "has_cross_attention") and downsample_block.has_cross_attention: + # For t2i-adapter CrossAttnDownBlock2D + additional_residuals = {} + if is_adapter and len(down_intrablock_additional_residuals) > 0: + additional_residuals["additional_residuals"] = down_intrablock_additional_residuals.pop(0) + + sample, res_samples = downsample_block( + hidden_states=sample, + temb=emb, + encoder_hidden_states=encoder_hidden_states, + attention_mask=attention_mask, + cross_attention_kwargs=cross_attention_kwargs, + encoder_attention_mask=encoder_attention_mask, + **additional_residuals, + ) + # import ipdb; ipdb.set_trace() + else: + sample, res_samples = downsample_block(hidden_states=sample, temb=emb, scale=lora_scale) + if is_adapter and len(down_intrablock_additional_residuals) > 0: + sample += down_intrablock_additional_residuals.pop(0) + + down_block_res_samples += res_samples + + if is_controlnet: + new_down_block_res_samples = () + + for down_block_res_sample, down_block_additional_residual in zip( + down_block_res_samples, down_block_additional_residuals + ): + down_block_res_sample = down_block_res_sample + down_block_additional_residual + new_down_block_res_samples = new_down_block_res_samples + (down_block_res_sample,) + + down_block_res_samples = new_down_block_res_samples + # 4. mid + if self.mid_block is not None: + if hasattr(self.mid_block, "has_cross_attention") and self.mid_block.has_cross_attention: + sample = self.mid_block( + sample, + emb, + encoder_hidden_states=encoder_hidden_states, + attention_mask=attention_mask, + cross_attention_kwargs=cross_attention_kwargs, + encoder_attention_mask=encoder_attention_mask, + ) + else: + sample = self.mid_block(sample, emb) + + # To support T2I-Adapter-XL + if ( + is_adapter + and len(down_intrablock_additional_residuals) > 0 + and sample.shape == down_intrablock_additional_residuals[0].shape + ): + sample += down_intrablock_additional_residuals.pop(0) + + if is_controlnet: + sample = sample + mid_block_additional_residual + # import ipdb; ipdb.set_trace() + # 5. up + for i, upsample_block in enumerate(self.up_blocks): + is_final_block = i == len(self.up_blocks) - 1 + + res_samples = down_block_res_samples[-len(upsample_block.resnets) :] + down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)] + + # if we have not reached the final block and need to forward the + # upsample size, we do it here + if not is_final_block and forward_upsample_size: + upsample_size = down_block_res_samples[-1].shape[2:] + + if hasattr(upsample_block, "has_cross_attention") and upsample_block.has_cross_attention: + sample = upsample_block( + hidden_states=sample, + temb=emb, + res_hidden_states_tuple=res_samples, + encoder_hidden_states=encoder_hidden_states, + cross_attention_kwargs=cross_attention_kwargs, + upsample_size=upsample_size, + attention_mask=attention_mask, + encoder_attention_mask=encoder_attention_mask, + ) + else: + sample = upsample_block( + hidden_states=sample, + temb=emb, + res_hidden_states_tuple=res_samples, + upsample_size=upsample_size, + scale=lora_scale, + ) + # import ipdb; ipdb.set_trace() + # 6. post-process + if self.conv_norm_out: + sample = self.conv_norm_out(sample) + sample = self.conv_act(sample) + sample = self.conv_out(sample) + + if USE_PEFT_BACKEND: + # remove `lora_scale` from each PEFT layer + unscale_lora_layers(self, lora_scale) + + if not return_dict: + return (sample,) + # import ipdb; ipdb.set_trace() + return UNet2DConditionOutput(sample=sample) \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/data/greatesthit.py b/foleycrafter/models/specvqgan/data/greatesthit.py new file mode 100644 index 0000000000000000000000000000000000000000..5c4ac159e0d21de91d0752557b4b03a905855dba --- /dev/null +++ b/foleycrafter/models/specvqgan/data/greatesthit.py @@ -0,0 +1,993 @@ +from matplotlib import collections +import json +import os +import copy +import matplotlib.pyplot as plt +import torch +from torchvision import transforms +import numpy as np +from tqdm import tqdm +from random import sample +import torchaudio +import logging +import collections +from glob import glob +import sys +import albumentations +import soundfile + +sys.path.insert(0, '.') # nopep8 +from train import instantiate_from_config +from foleycrafter.models.specvqgan.data.transforms import * + +torchaudio.set_audio_backend("sox_io") +logger = logging.getLogger(f'main.{__name__}') + +SR = 22050 +FPS = 15 +MAX_SAMPLE_ITER = 10 + +def non_negative(x): return int(np.round(max(0, x), 0)) + +def rms(x): return np.sqrt(np.mean(x**2)) + +def get_GH_data_identifier(video_name, start_idx, split='_'): + if isinstance(start_idx, str): + return video_name + split + start_idx + elif isinstance(start_idx, int): + return video_name + split + str(start_idx) + else: + raise NotImplementedError + + +class Crop(object): + + def __init__(self, cropped_shape=None, random_crop=False): + self.cropped_shape = cropped_shape + if cropped_shape is not None: + mel_num, spec_len = cropped_shape + if random_crop: + self.cropper = albumentations.RandomCrop + else: + self.cropper = albumentations.CenterCrop + self.preprocessor = albumentations.Compose([self.cropper(mel_num, spec_len)]) + else: + self.preprocessor = lambda **kwargs: kwargs + + def __call__(self, item): + item['image'] = self.preprocessor(image=item['image'])['image'] + if 'cond_image' in item.keys(): + item['cond_image'] = self.preprocessor(image=item['cond_image'])['image'] + return item + +class CropImage(Crop): + def __init__(self, *crop_args): + super().__init__(*crop_args) + +class CropFeats(Crop): + def __init__(self, *crop_args): + super().__init__(*crop_args) + + def __call__(self, item): + item['feature'] = self.preprocessor(image=item['feature'])['image'] + return item + +class CropCoords(Crop): + def __init__(self, *crop_args): + super().__init__(*crop_args) + + def __call__(self, item): + item['coord'] = self.preprocessor(image=item['coord'])['image'] + return item + +class ResampleFrames(object): + def __init__(self, feat_sample_size, times_to_repeat_after_resample=None): + self.feat_sample_size = feat_sample_size + self.times_to_repeat_after_resample = times_to_repeat_after_resample + + def __call__(self, item): + feat_len = item['feature'].shape[0] + + ## resample + assert feat_len >= self.feat_sample_size + # evenly spaced points (abcdefghkl -> aoooofoooo) + idx = np.linspace(0, feat_len, self.feat_sample_size, dtype=np.int, endpoint=False) + # xoooo xoooo -> ooxoo ooxoo + shift = feat_len // (self.feat_sample_size + 1) + idx = idx + shift + + ## repeat after resampling (abc -> aaaabbbbcccc) + if self.times_to_repeat_after_resample is not None and self.times_to_repeat_after_resample > 1: + idx = np.repeat(idx, self.times_to_repeat_after_resample) + + item['feature'] = item['feature'][idx, :] + return item + + +class GreatestHitSpecs(torch.utils.data.Dataset): + + def __init__(self, split, spec_dir_path, spec_len, random_crop, mel_num, + spec_crop_len, L=2.0, rand_shift=False, spec_transforms=None, splits_path='./data', + meta_path='./data/info_r2plus1d_dim1024_15fps.json'): + super().__init__() + self.split = split + self.specs_dir = spec_dir_path + self.spec_transforms = spec_transforms + self.splits_path = splits_path + self.meta_path = meta_path + self.spec_len = spec_len + self.rand_shift = rand_shift + self.L = L + self.spec_take_first = int(math.ceil(860 * (L / 10.) / 32) * 32) + self.spec_take_first = 860 if self.spec_take_first > 860 else self.spec_take_first + + greatesthit_meta = json.load(open(self.meta_path, 'r')) + unique_classes = sorted(list(set(ht for ht in greatesthit_meta['hit_type']))) + self.label2target = {label: target for target, label in enumerate(unique_classes)} + self.target2label = {target: label for label, target in self.label2target.items()} + self.video_idx2label = { + get_GH_data_identifier(greatesthit_meta['video_name'][i], greatesthit_meta['start_idx'][i]): + greatesthit_meta['hit_type'][i] for i in range(len(greatesthit_meta['video_name'])) + } + self.available_video_hit = list(self.video_idx2label.keys()) + self.video_idx2path = { + vh: os.path.join(self.specs_dir, + vh.replace('_', '_denoised_') + '_' + self.video_idx2label[vh].replace(' ', '_') +'_mel.npy') + for vh in self.available_video_hit + } + self.video_idx2idx = { + get_GH_data_identifier(greatesthit_meta['video_name'][i], greatesthit_meta['start_idx'][i]): + i for i in range(len(greatesthit_meta['video_name'])) + } + + split_clip_ids_path = os.path.join(splits_path, f'greatesthit_{split}.json') + if not os.path.exists(split_clip_ids_path): + raise NotImplementedError() + clip_video_hit = json.load(open(split_clip_ids_path, 'r')) + self.dataset = clip_video_hit + spec_crop_len = self.spec_take_first if self.spec_take_first <= spec_crop_len else spec_crop_len + self.spec_transforms = transforms.Compose([ + CropImage([mel_num, spec_crop_len], random_crop), + # transforms.RandomApply([FrequencyMasking(freq_mask_param=20)], p=0), + # transforms.RandomApply([TimeMasking(time_mask_param=int(32 * self.L))], p=0) + ]) + + self.video2indexes = {} + for video_idx in self.dataset: + video, start_idx = video_idx.split('_') + if video not in self.video2indexes.keys(): + self.video2indexes[video] = [] + self.video2indexes[video].append(start_idx) + for video in self.video2indexes.keys(): + if len(self.video2indexes[video]) == 1: # given video contains only one hit + self.dataset.remove( + get_GH_data_identifier(video, self.video2indexes[video][0]) + ) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + item = {} + + video_idx = self.dataset[idx] + spec_path = self.video_idx2path[video_idx] + spec = np.load(spec_path) # (80, 860) + + if self.rand_shift: + shift = random.uniform(0, 0.5) + spec_shift = int(shift * spec.shape[1] // 10) + # Since only the first second is used + spec = np.roll(spec, -spec_shift, 1) + + # concat spec outside dataload + item['image'] = 2 * spec - 1 # (80, 860) + item['image'] = item['image'][:, :self.spec_take_first] + item['file_path'] = spec_path + + item['label'] = self.video_idx2label[video_idx] + item['target'] = self.label2target[item['label']] + + if self.spec_transforms is not None: + item = self.spec_transforms(item) + + return item + + +class GreatestHitSpecsTrain(GreatestHitSpecs): + def __init__(self, specs_dataset_cfg): + super().__init__('train', **specs_dataset_cfg) + +class GreatestHitSpecsValidation(GreatestHitSpecs): + def __init__(self, specs_dataset_cfg): + super().__init__('val', **specs_dataset_cfg) + +class GreatestHitSpecsTest(GreatestHitSpecs): + def __init__(self, specs_dataset_cfg): + super().__init__('test', **specs_dataset_cfg) + + + +class GreatestHitWave(torch.utils.data.Dataset): + + def __init__(self, split, wav_dir, random_crop, mel_num, spec_crop_len, spec_len, + L=2.0, splits_path='./data', rand_shift=True, + data_path='data/greatesthit/greatesthit-process-resized'): + super().__init__() + self.split = split + self.wav_dir = wav_dir + self.splits_path = splits_path + self.data_path = data_path + self.L = L + self.rand_shift = rand_shift + + split_clip_ids_path = os.path.join(splits_path, f'greatesthit_{split}.json') + if not os.path.exists(split_clip_ids_path): + raise NotImplementedError() + clip_video_hit = json.load(open(split_clip_ids_path, 'r')) + + video_name = list(set([vidx.split('_')[0] for vidx in clip_video_hit])) + + self.video_frame_cnt = {v: len(os.listdir(os.path.join(self.data_path, v, 'frames'))) // 2 for v in video_name} + self.left_over = int(FPS * L + 1) + self.video_audio_path = {v: os.path.join(self.data_path, v, f'audio/{v}_denoised_resampled.wav') for v in video_name} + self.dataset = clip_video_hit + + self.video2indexes = {} + for video_idx in self.dataset: + video, start_idx = video_idx.split('_') + if video not in self.video2indexes.keys(): + self.video2indexes[video] = [] + self.video2indexes[video].append(start_idx) + for video in self.video2indexes.keys(): + if len(self.video2indexes[video]) == 1: # given video contains only one hit + self.dataset.remove( + get_GH_data_identifier(video, self.video2indexes[video][0]) + ) + + self.wav_transforms = transforms.Compose([ + MakeMono(), + Padding(target_len=int(SR * self.L)), + ]) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + item = {} + video_idx = self.dataset[idx] + video, start_idx = video_idx.split('_') + start_idx = int(start_idx) + if self.rand_shift: + shift = int(random.uniform(-0.5, 0.5) * SR) + start_idx = non_negative(start_idx + shift) + + wave_path = self.video_audio_path[video] + wav, sr = soundfile.read(wave_path, frames=int(SR * self.L), start=start_idx) + assert sr == SR + wav = self.wav_transforms(wav) + + item['image'] = wav # (44100,) + # item['wav'] = wav + item['file_path_wav_'] = wave_path + + item['label'] = 'None' + item['target'] = 'None' + + return item + + +class GreatestHitWaveTrain(GreatestHitWave): + def __init__(self, specs_dataset_cfg): + super().__init__('train', **specs_dataset_cfg) + +class GreatestHitWaveValidation(GreatestHitWave): + def __init__(self, specs_dataset_cfg): + super().__init__('val', **specs_dataset_cfg) + +class GreatestHitWaveTest(GreatestHitWave): + def __init__(self, specs_dataset_cfg): + super().__init__('test', **specs_dataset_cfg) + + +class CondGreatestHitSpecsCondOnImage(torch.utils.data.Dataset): + + def __init__(self, split, specs_dir, spec_len, feat_len, feat_depth, feat_crop_len, random_crop, mel_num, spec_crop_len, + vqgan_L=10.0, L=1.0, rand_shift=False, spec_transforms=None, frame_transforms=None, splits_path='./data', + meta_path='./data/info_r2plus1d_dim1024_15fps.json', frame_path='data/greatesthit/greatesthit_processed', + p_outside_cond=0., p_audio_aug=0.5): + super().__init__() + self.split = split + self.specs_dir = specs_dir + self.spec_transforms = spec_transforms + self.frame_transforms = frame_transforms + self.splits_path = splits_path + self.meta_path = meta_path + self.frame_path = frame_path + self.feat_len = feat_len + self.feat_depth = feat_depth + self.feat_crop_len = feat_crop_len + self.spec_len = spec_len + self.rand_shift = rand_shift + self.L = L + self.spec_take_first = int(math.ceil(860 * (vqgan_L / 10.) / 32) * 32) + self.spec_take_first = 860 if self.spec_take_first > 860 else self.spec_take_first + self.p_outside_cond = torch.tensor(p_outside_cond) + + greatesthit_meta = json.load(open(self.meta_path, 'r')) + unique_classes = sorted(list(set(ht for ht in greatesthit_meta['hit_type']))) + self.label2target = {label: target for target, label in enumerate(unique_classes)} + self.target2label = {target: label for label, target in self.label2target.items()} + self.video_idx2label = { + get_GH_data_identifier(greatesthit_meta['video_name'][i], greatesthit_meta['start_idx'][i]): + greatesthit_meta['hit_type'][i] for i in range(len(greatesthit_meta['video_name'])) + } + self.available_video_hit = list(self.video_idx2label.keys()) + self.video_idx2path = { + vh: os.path.join(self.specs_dir, + vh.replace('_', '_denoised_') + '_' + self.video_idx2label[vh].replace(' ', '_') +'_mel.npy') + for vh in self.available_video_hit + } + for value in self.video_idx2path.values(): + assert os.path.exists(value) + self.video_idx2idx = { + get_GH_data_identifier(greatesthit_meta['video_name'][i], greatesthit_meta['start_idx'][i]): + i for i in range(len(greatesthit_meta['video_name'])) + } + + split_clip_ids_path = os.path.join(splits_path, f'greatesthit_{split}.json') + if not os.path.exists(split_clip_ids_path): + self.make_split_files() + clip_video_hit = json.load(open(split_clip_ids_path, 'r')) + self.dataset = clip_video_hit + spec_crop_len = self.spec_take_first if self.spec_take_first <= spec_crop_len else spec_crop_len + self.spec_transforms = transforms.Compose([ + CropImage([mel_num, spec_crop_len], random_crop), + # transforms.RandomApply([FrequencyMasking(freq_mask_param=20)], p=p_audio_aug), + # transforms.RandomApply([TimeMasking(time_mask_param=int(32 * self.L))], p=p_audio_aug) + ]) + if self.frame_transforms == None: + self.frame_transforms = transforms.Compose([ + Resize3D(128), + RandomResizedCrop3D(112, scale=(0.5, 1.0)), + RandomHorizontalFlip3D(), + ColorJitter3D(brightness=0.1, saturation=0.1), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + + self.video2indexes = {} + for video_idx in self.dataset: + video, start_idx = video_idx.split('_') + if video not in self.video2indexes.keys(): + self.video2indexes[video] = [] + self.video2indexes[video].append(start_idx) + for video in self.video2indexes.keys(): + if len(self.video2indexes[video]) == 1: # given video contains only one hit + self.dataset.remove( + get_GH_data_identifier(video, self.video2indexes[video][0]) + ) + + clip_classes = [self.label2target[self.video_idx2label[vh]] for vh in clip_video_hit] + class2count = collections.Counter(clip_classes) + self.class_counts = torch.tensor([class2count[cls] for cls in range(len(class2count))]) + if self.L != 1.0: + print(split, L) + self.validate_data() + self.video2indexes = {} + for video_idx in self.dataset: + video, start_idx = video_idx.split('_') + if video not in self.video2indexes.keys(): + self.video2indexes[video] = [] + self.video2indexes[video].append(start_idx) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + item = {} + + try: + video_idx = self.dataset[idx] + spec_path = self.video_idx2path[video_idx] + spec = np.load(spec_path) # (80, 860) + + video, start_idx = video_idx.split('_') + frame_path = os.path.join(self.frame_path, video, 'frames') + start_frame_idx = non_negative(FPS * int(start_idx)/SR) + end_frame_idx = non_negative(start_frame_idx + FPS * self.L) + + if self.rand_shift: + shift = random.uniform(0, 0.5) + spec_shift = int(shift * spec.shape[1] // 10) + # Since only the first second is used + spec = np.roll(spec, -spec_shift, 1) + start_frame_idx += int(FPS * shift) + end_frame_idx += int(FPS * shift) + + frames = [Image.open(os.path.join( + frame_path, f'frame{i+1:0>6d}.jpg')).convert('RGB') for i in + range(start_frame_idx, end_frame_idx)] + + # Sample condition + if torch.all(torch.bernoulli(self.p_outside_cond) == 1.): + # Sample condition from outside video + all_idx = set(list(range(len(self.dataset)))) + all_idx.remove(idx) + cond_video_idx = self.dataset[sample(all_idx, k=1)[0]] + cond_video, cond_start_idx = cond_video_idx.split('_') + else: + cond_video = video + video_hits_idx = copy.copy(self.video2indexes[video]) + video_hits_idx.remove(start_idx) + cond_start_idx = sample(video_hits_idx, k=1)[0] + cond_video_idx = get_GH_data_identifier(cond_video, cond_start_idx) + + cond_spec_path = self.video_idx2path[cond_video_idx] + cond_spec = np.load(cond_spec_path) # (80, 860) + + cond_video, cond_start_idx = cond_video_idx.split('_') + cond_frame_path = os.path.join(self.frame_path, cond_video, 'frames') + cond_start_frame_idx = non_negative(FPS * int(cond_start_idx)/SR) + cond_end_frame_idx = non_negative(cond_start_frame_idx + FPS * self.L) + + if self.rand_shift: + cond_shift = random.uniform(0, 0.5) + cond_spec_shift = int(cond_shift * cond_spec.shape[1] // 10) + # Since only the first second is used + cond_spec = np.roll(cond_spec, -cond_spec_shift, 1) + cond_start_frame_idx += int(FPS * cond_shift) + cond_end_frame_idx += int(FPS * cond_shift) + + cond_frames = [Image.open(os.path.join( + cond_frame_path, f'frame{i+1:0>6d}.jpg')).convert('RGB') for i in + range(cond_start_frame_idx, cond_end_frame_idx)] + + # concat spec outside dataload + item['image'] = 2 * spec - 1 # (80, 860) + item['cond_image'] = 2 * cond_spec - 1 # (80, 860) + item['image'] = item['image'][:, :self.spec_take_first] + item['cond_image'] = item['cond_image'][:, :self.spec_take_first] + item['file_path_specs_'] = spec_path + item['file_path_cond_specs_'] = cond_spec_path + + if self.frame_transforms is not None: + cond_frames = self.frame_transforms(cond_frames) + frames = self.frame_transforms(frames) + + item['feature'] = np.stack(cond_frames + frames, axis=0) # (30 * L, 112, 112, 3) + item['file_path_feats_'] = (frame_path, start_frame_idx) + item['file_path_cond_feats_'] = (cond_frame_path, cond_start_frame_idx) + + item['label'] = self.video_idx2label[video_idx] + item['target'] = self.label2target[item['label']] + + if self.spec_transforms is not None: + item = self.spec_transforms(item) + except Exception: + print(sys.exc_info()[2]) + print('!!!!!!!!!!!!!!!!!!!!', video_idx, cond_video_idx) + print('!!!!!!!!!!!!!!!!!!!!', end_frame_idx, cond_end_frame_idx) + exit(1) + + return item + + + def validate_data(self): + original_len = len(self.dataset) + valid_dataset = [] + for video_idx in tqdm(self.dataset): + video, start_idx = video_idx.split('_') + frame_path = os.path.join(self.frame_path, video, 'frames') + start_frame_idx = non_negative(FPS * int(start_idx)/SR) + end_frame_idx = non_negative(start_frame_idx + FPS * (self.L + 0.6)) + if os.path.exists(os.path.join(frame_path, f'frame{end_frame_idx:0>6d}.jpg')): + valid_dataset.append(video_idx) + else: + self.video2indexes[video].remove(start_idx) + for video_idx in valid_dataset: + video, start_idx = video_idx.split('_') + if len(self.video2indexes[video]) == 1: + valid_dataset.remove(video_idx) + if original_len != len(valid_dataset): + print(f'Validated dataset with enough frames: {len(valid_dataset)}') + self.dataset = valid_dataset + split_clip_ids_path = os.path.join(self.splits_path, f'greatesthit_{self.split}_{self.L:.2f}.json') + if not os.path.exists(split_clip_ids_path): + with open(split_clip_ids_path, 'w') as f: + json.dump(valid_dataset, f) + + + def make_split_files(self, ratio=[0.85, 0.1, 0.05]): + random.seed(1337) + print(f'The split files do not exist @ {self.splits_path}. Calculating the new ones.') + # The downloaded videos (some went missing on YouTube and no longer available) + available_mel_paths = set(glob(os.path.join(self.specs_dir, '*_mel.npy'))) + self.available_video_hit = [vh for vh in self.available_video_hit if self.video_idx2path[vh] in available_mel_paths] + + all_video = list(self.video2indexes.keys()) + + print(f'The number of clips available after download: {len(self.available_video_hit)}') + print(f'The number of videos available after download: {len(all_video)}') + + available_idx = list(range(len(all_video))) + random.shuffle(available_idx) + assert sum(ratio) == 1. + cut_train = int(ratio[0] * len(all_video)) + cut_test = cut_train + int(ratio[1] * len(all_video)) + + train_idx = available_idx[:cut_train] + test_idx = available_idx[cut_train:cut_test] + valid_idx = available_idx[cut_test:] + + train_video = [all_video[i] for i in train_idx] + test_video = [all_video[i] for i in test_idx] + valid_video = [all_video[i] for i in valid_idx] + + train_video_hit = [] + for v in train_video: + train_video_hit += [get_GH_data_identifier(v, hit_idx) for hit_idx in self.video2indexes[v]] + test_video_hit = [] + for v in test_video: + test_video_hit += [get_GH_data_identifier(v, hit_idx) for hit_idx in self.video2indexes[v]] + valid_video_hit = [] + for v in valid_video: + valid_video_hit += [get_GH_data_identifier(v, hit_idx) for hit_idx in self.video2indexes[v]] + + # mix train and valid for better validation loss + mixed = train_video_hit + valid_video_hit + random.shuffle(mixed) + split = int(len(mixed) * ratio[0] / (ratio[0] + ratio[2])) + train_video_hit = mixed[:split] + valid_video_hit = mixed[split:] + + with open(os.path.join(self.splits_path, 'greatesthit_train.json'), 'w') as train_file,\ + open(os.path.join(self.splits_path, 'greatesthit_test.json'), 'w') as test_file,\ + open(os.path.join(self.splits_path, 'greatesthit_valid.json'), 'w') as valid_file: + json.dump(train_video_hit, train_file) + json.dump(test_video_hit, test_file) + json.dump(valid_video_hit, valid_file) + + print(f'Put {len(train_idx)} clips to the train set and saved it to ./data/greatesthit_train.json') + print(f'Put {len(test_idx)} clips to the test set and saved it to ./data/greatesthit_test.json') + print(f'Put {len(valid_idx)} clips to the valid set and saved it to ./data/greatesthit_valid.json') + + +class CondGreatestHitSpecsCondOnImageTrain(CondGreatestHitSpecsCondOnImage): + def __init__(self, dataset_cfg): + train_transforms = transforms.Compose([ + Resize3D(256), + RandomResizedCrop3D(224, scale=(0.5, 1.0)), + RandomHorizontalFlip3D(), + ColorJitter3D(brightness=0.1, saturation=0.1), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('train', frame_transforms=train_transforms, **dataset_cfg) + +class CondGreatestHitSpecsCondOnImageValidation(CondGreatestHitSpecsCondOnImage): + def __init__(self, dataset_cfg): + valid_transforms = transforms.Compose([ + Resize3D(256), + CenterCrop3D(224), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('val', frame_transforms=valid_transforms, **dataset_cfg) + +class CondGreatestHitSpecsCondOnImageTest(CondGreatestHitSpecsCondOnImage): + def __init__(self, dataset_cfg): + test_transforms = transforms.Compose([ + Resize3D(256), + CenterCrop3D(224), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('test', frame_transforms=test_transforms, **dataset_cfg) + + +class CondGreatestHitWaveCondOnImage(torch.utils.data.Dataset): + + def __init__(self, split, wav_dir, spec_len, random_crop, mel_num, spec_crop_len, + L=2.0, frame_transforms=None, splits_path='./data', + data_path='data/greatesthit/greatesthit-process-resized', + p_outside_cond=0., p_audio_aug=0.5, rand_shift=True): + super().__init__() + self.split = split + self.wav_dir = wav_dir + self.frame_transforms = frame_transforms + self.splits_path = splits_path + self.data_path = data_path + self.spec_len = spec_len + self.L = L + self.rand_shift = rand_shift + self.p_outside_cond = torch.tensor(p_outside_cond) + + split_clip_ids_path = os.path.join(splits_path, f'greatesthit_{split}.json') + if not os.path.exists(split_clip_ids_path): + raise NotImplementedError() + clip_video_hit = json.load(open(split_clip_ids_path, 'r')) + + video_name = list(set([vidx.split('_')[0] for vidx in clip_video_hit])) + + self.video_frame_cnt = {v: len(os.listdir(os.path.join(self.data_path, v, 'frames')))//2 for v in video_name} + self.left_over = int(FPS * L + 1) + self.video_audio_path = {v: os.path.join(self.data_path, v, f'audio/{v}_denoised_resampled.wav') for v in video_name} + self.dataset = clip_video_hit + + self.video2indexes = {} + for video_idx in self.dataset: + video, start_idx = video_idx.split('_') + if video not in self.video2indexes.keys(): + self.video2indexes[video] = [] + self.video2indexes[video].append(start_idx) + for video in self.video2indexes.keys(): + if len(self.video2indexes[video]) == 1: # given video contains only one hit + self.dataset.remove( + get_GH_data_identifier(video, self.video2indexes[video][0]) + ) + + self.wav_transforms = transforms.Compose([ + MakeMono(), + Padding(target_len=int(SR * self.L)), + ]) + if self.frame_transforms == None: + self.frame_transforms = transforms.Compose([ + Resize3D(256), + RandomResizedCrop3D(224, scale=(0.5, 1.0)), + RandomHorizontalFlip3D(), + ColorJitter3D(brightness=0.1, saturation=0.1), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + item = {} + video_idx = self.dataset[idx] + video, start_idx = video_idx.split('_') + start_idx = int(start_idx) + frame_path = os.path.join(self.data_path, video, 'frames') + start_frame_idx = non_negative(FPS * int(start_idx)/SR) + if self.rand_shift: + shift = random.uniform(-0.5, 0.5) + start_frame_idx = non_negative(start_frame_idx + int(FPS * shift)) + start_idx = non_negative(start_idx + int(SR * shift)) + if start_frame_idx > self.video_frame_cnt[video] - self.left_over: + start_frame_idx = self.video_frame_cnt[video] - self.left_over + start_idx = non_negative(SR * (start_frame_idx / FPS)) + + end_frame_idx = non_negative(start_frame_idx + FPS * self.L) + + # target + wave_path = self.video_audio_path[video] + frames = [Image.open(os.path.join( + frame_path, f'frame{i+1:0>6d}')).convert('RGB') for i in + range(start_frame_idx, end_frame_idx)] + wav, sr = soundfile.read(wave_path, frames=int(SR * self.L), start=start_idx) + assert sr == SR + wav = self.wav_transforms(wav) + + # cond + if torch.all(torch.bernoulli(self.p_outside_cond) == 1.): + all_idx = set(list(range(len(self.dataset)))) + all_idx.remove(idx) + cond_video_idx = self.dataset[sample(all_idx, k=1)[0]] + cond_video, cond_start_idx = cond_video_idx.split('_') + else: + cond_video = video + video_hits_idx = copy.copy(self.video2indexes[video]) + if str(start_idx) in video_hits_idx: + video_hits_idx.remove(str(start_idx)) + cond_start_idx = sample(video_hits_idx, k=1)[0] + cond_video_idx = get_GH_data_identifier(cond_video, cond_start_idx) + + cond_video, cond_start_idx = cond_video_idx.split('_') + cond_start_idx = int(cond_start_idx) + cond_frame_path = os.path.join(self.data_path, cond_video, 'frames') + cond_start_frame_idx = non_negative(FPS * int(cond_start_idx)/SR) + cond_wave_path = self.video_audio_path[cond_video] + + if self.rand_shift: + cond_shift = random.uniform(-0.5, 0.5) + cond_start_frame_idx = non_negative(cond_start_frame_idx + int(FPS * cond_shift)) + cond_start_idx = non_negative(cond_start_idx + int(shift * SR)) + if cond_start_frame_idx > self.video_frame_cnt[cond_video] - self.left_over: + cond_start_frame_idx = self.video_frame_cnt[cond_video] - self.left_over + cond_start_idx = non_negative(SR * (cond_start_frame_idx / FPS)) + cond_end_frame_idx = non_negative(cond_start_frame_idx + FPS * self.L) + + cond_frames = [Image.open(os.path.join( + cond_frame_path, f'frame{i+1:0>6d}')).convert('RGB') for i in + range(cond_start_frame_idx, cond_end_frame_idx)] + cond_wav, _ = soundfile.read(cond_wave_path, frames=int(SR * self.L), start=cond_start_idx) + cond_wav = self.wav_transforms(cond_wav) + + item['image'] = wav # (44100,) + item['cond_image'] = cond_wav # (44100,) + item['file_path_wav_'] = wave_path + item['file_path_cond_wav_'] = cond_wave_path + + if self.frame_transforms is not None: + cond_frames = self.frame_transforms(cond_frames) + frames = self.frame_transforms(frames) + + item['feature'] = np.stack(cond_frames + frames, axis=0) # (30 * L, 112, 112, 3) + item['file_path_feats_'] = (frame_path, start_idx) + item['file_path_cond_feats_'] = (cond_frame_path, cond_start_idx) + + item['label'] = 'None' + item['target'] = 'None' + + return item + + def validate_data(self): + raise NotImplementedError() + + def make_split_files(self, ratio=[0.85, 0.1, 0.05]): + random.seed(1337) + print(f'The split files do not exist @ {self.splits_path}. Calculating the new ones.') + + all_video = sorted(os.listdir(self.data_path)) + print(f'The number of videos available after download: {len(all_video)}') + + available_idx = list(range(len(all_video))) + random.shuffle(available_idx) + assert sum(ratio) == 1. + cut_train = int(ratio[0] * len(all_video)) + cut_test = cut_train + int(ratio[1] * len(all_video)) + + train_idx = available_idx[:cut_train] + test_idx = available_idx[cut_train:cut_test] + valid_idx = available_idx[cut_test:] + + train_video = [all_video[i] for i in train_idx] + test_video = [all_video[i] for i in test_idx] + valid_video = [all_video[i] for i in valid_idx] + + with open(os.path.join(self.splits_path, 'greatesthit_video_train.json'), 'w') as train_file,\ + open(os.path.join(self.splits_path, 'greatesthit_video_test.json'), 'w') as test_file,\ + open(os.path.join(self.splits_path, 'greatesthit_video_valid.json'), 'w') as valid_file: + json.dump(train_video, train_file) + json.dump(test_video, test_file) + json.dump(valid_video, valid_file) + + print(f'Put {len(train_idx)} videos to the train set and saved it to ./data/greatesthit_video_train.json') + print(f'Put {len(test_idx)} videos to the test set and saved it to ./data/greatesthit_video_test.json') + print(f'Put {len(valid_idx)} videos to the valid set and saved it to ./data/greatesthit_video_valid.json') + + +class CondGreatestHitWaveCondOnImageTrain(CondGreatestHitWaveCondOnImage): + def __init__(self, dataset_cfg): + train_transforms = transforms.Compose([ + Resize3D(128), + RandomResizedCrop3D(112, scale=(0.5, 1.0)), + RandomHorizontalFlip3D(), + ColorJitter3D(brightness=0.4, saturation=0.4, contrast=0.2, hue=0.1), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('train', frame_transforms=train_transforms, **dataset_cfg) + +class CondGreatestHitWaveCondOnImageValidation(CondGreatestHitWaveCondOnImage): + def __init__(self, dataset_cfg): + valid_transforms = transforms.Compose([ + Resize3D(128), + CenterCrop3D(112), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('val', frame_transforms=valid_transforms, **dataset_cfg) + +class CondGreatestHitWaveCondOnImageTest(CondGreatestHitWaveCondOnImage): + def __init__(self, dataset_cfg): + test_transforms = transforms.Compose([ + Resize3D(128), + CenterCrop3D(112), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('test', frame_transforms=test_transforms, **dataset_cfg) + + + +class GreatestHitWaveCondOnImage(torch.utils.data.Dataset): + + def __init__(self, split, wav_dir, spec_len, random_crop, mel_num, spec_crop_len, + L=2.0, frame_transforms=None, splits_path='./data', + data_path='data/greatesthit/greatesthit-process-resized', + p_outside_cond=0., p_audio_aug=0.5, rand_shift=True): + super().__init__() + self.split = split + self.wav_dir = wav_dir + self.frame_transforms = frame_transforms + self.splits_path = splits_path + self.data_path = data_path + self.spec_len = spec_len + self.L = L + self.rand_shift = rand_shift + self.p_outside_cond = torch.tensor(p_outside_cond) + + split_clip_ids_path = os.path.join(splits_path, f'greatesthit_{split}.json') + if not os.path.exists(split_clip_ids_path): + raise NotImplementedError() + clip_video_hit = json.load(open(split_clip_ids_path, 'r')) + + video_name = list(set([vidx.split('_')[0] for vidx in clip_video_hit])) + + self.video_frame_cnt = {v: len(os.listdir(os.path.join(self.data_path, v, 'frames')))//2 for v in video_name} + self.left_over = int(FPS * L + 1) + self.video_audio_path = {v: os.path.join(self.data_path, v, f'audio/{v}_denoised_resampled.wav') for v in video_name} + self.dataset = clip_video_hit + + self.video2indexes = {} + for video_idx in self.dataset: + video, start_idx = video_idx.split('_') + if video not in self.video2indexes.keys(): + self.video2indexes[video] = [] + self.video2indexes[video].append(start_idx) + for video in self.video2indexes.keys(): + if len(self.video2indexes[video]) == 1: # given video contains only one hit + self.dataset.remove( + get_GH_data_identifier(video, self.video2indexes[video][0]) + ) + + self.wav_transforms = transforms.Compose([ + MakeMono(), + Padding(target_len=int(SR * self.L)), + ]) + if self.frame_transforms == None: + self.frame_transforms = transforms.Compose([ + Resize3D(256), + RandomResizedCrop3D(224, scale=(0.5, 1.0)), + RandomHorizontalFlip3D(), + ColorJitter3D(brightness=0.1, saturation=0.1), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + item = {} + video_idx = self.dataset[idx] + video, start_idx = video_idx.split('_') + start_idx = int(start_idx) + frame_path = os.path.join(self.data_path, video, 'frames') + start_frame_idx = non_negative(FPS * int(start_idx)/SR) + if self.rand_shift: + shift = random.uniform(-0.5, 0.5) + start_frame_idx = non_negative(start_frame_idx + int(FPS * shift)) + start_idx = non_negative(start_idx + int(SR * shift)) + if start_frame_idx > self.video_frame_cnt[video] - self.left_over: + start_frame_idx = self.video_frame_cnt[video] - self.left_over + start_idx = non_negative(SR * (start_frame_idx / FPS)) + + end_frame_idx = non_negative(start_frame_idx + FPS * self.L) + + # target + wave_path = self.video_audio_path[video] + frames = [Image.open(os.path.join( + frame_path, f'frame{i+1:0>6d}')).convert('RGB') for i in + range(start_frame_idx, end_frame_idx)] + wav, sr = soundfile.read(wave_path, frames=int(SR * self.L), start=start_idx) + assert sr == SR + wav = self.wav_transforms(wav) + + item['image'] = wav # (44100,) + item['file_path_wav_'] = wave_path + + if self.frame_transforms is not None: + frames = self.frame_transforms(frames) + + item['feature'] = torch.stack(frames, dim=0) # (15 * L, 112, 112, 3) + item['file_path_feats_'] = (frame_path, start_idx) + + item['label'] = 'None' + item['target'] = 'None' + + return item + + def validate_data(self): + raise NotImplementedError() + + def make_split_files(self, ratio=[0.85, 0.1, 0.05]): + random.seed(1337) + print(f'The split files do not exist @ {self.splits_path}. Calculating the new ones.') + + all_video = sorted(os.listdir(self.data_path)) + print(f'The number of videos available after download: {len(all_video)}') + + available_idx = list(range(len(all_video))) + random.shuffle(available_idx) + assert sum(ratio) == 1. + cut_train = int(ratio[0] * len(all_video)) + cut_test = cut_train + int(ratio[1] * len(all_video)) + + train_idx = available_idx[:cut_train] + test_idx = available_idx[cut_train:cut_test] + valid_idx = available_idx[cut_test:] + + train_video = [all_video[i] for i in train_idx] + test_video = [all_video[i] for i in test_idx] + valid_video = [all_video[i] for i in valid_idx] + + with open(os.path.join(self.splits_path, 'greatesthit_video_train.json'), 'w') as train_file,\ + open(os.path.join(self.splits_path, 'greatesthit_video_test.json'), 'w') as test_file,\ + open(os.path.join(self.splits_path, 'greatesthit_video_valid.json'), 'w') as valid_file: + json.dump(train_video, train_file) + json.dump(test_video, test_file) + json.dump(valid_video, valid_file) + + print(f'Put {len(train_idx)} videos to the train set and saved it to ./data/greatesthit_video_train.json') + print(f'Put {len(test_idx)} videos to the test set and saved it to ./data/greatesthit_video_test.json') + print(f'Put {len(valid_idx)} videos to the valid set and saved it to ./data/greatesthit_video_valid.json') + + +class GreatestHitWaveCondOnImageTrain(GreatestHitWaveCondOnImage): + def __init__(self, dataset_cfg): + train_transforms = transforms.Compose([ + Resize3D(128), + RandomResizedCrop3D(112, scale=(0.5, 1.0)), + RandomHorizontalFlip3D(), + ColorJitter3D(brightness=0.4, saturation=0.4, contrast=0.2, hue=0.1), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('train', frame_transforms=train_transforms, **dataset_cfg) + +class GreatestHitWaveCondOnImageValidation(GreatestHitWaveCondOnImage): + def __init__(self, dataset_cfg): + valid_transforms = transforms.Compose([ + Resize3D(128), + CenterCrop3D(112), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('val', frame_transforms=valid_transforms, **dataset_cfg) + +class GreatestHitWaveCondOnImageTest(GreatestHitWaveCondOnImage): + def __init__(self, dataset_cfg): + test_transforms = transforms.Compose([ + Resize3D(128), + CenterCrop3D(112), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('test', frame_transforms=test_transforms, **dataset_cfg) + + +def draw_spec(spec, dest, cmap='magma'): + plt.imshow(spec, cmap=cmap, origin='lower') + plt.axis('off') + plt.savefig(dest, bbox_inches='tight', pad_inches=0., dpi=300) + plt.close() + +if __name__ == '__main__': + import sys + + from omegaconf import OmegaConf + + # cfg = OmegaConf.load('configs/greatesthit_transformer_with_vNet_randshift_2s_GH_vqgan_no_earlystop.yaml') + cfg = OmegaConf.load('configs/greatesthit_codebook.yaml') + data = instantiate_from_config(cfg.data) + data.prepare_data() + data.setup() + print(len(data.datasets['train'])) + print(data.datasets['train'][24]) + diff --git a/foleycrafter/models/specvqgan/data/impactset.py b/foleycrafter/models/specvqgan/data/impactset.py new file mode 100644 index 0000000000000000000000000000000000000000..039dc764260c05ab816c2c79098eba9ef1ffd442 --- /dev/null +++ b/foleycrafter/models/specvqgan/data/impactset.py @@ -0,0 +1,778 @@ +import json +import os +import matplotlib.pyplot as plt +import torch +from torchvision import transforms +import numpy as np +from tqdm import tqdm +from random import sample +import torchaudio +import logging +from glob import glob +import sys +import soundfile +import copy +import csv +import noisereduce as nr + +sys.path.insert(0, '.') # nopep8 +from train import instantiate_from_config +from foleycrafter.models.specvqgan.data.transforms import * + +torchaudio.set_audio_backend("sox_io") +logger = logging.getLogger(f'main.{__name__}') + +SR = 22050 +FPS = 15 +MAX_SAMPLE_ITER = 10 + +def non_negative(x): return int(np.round(max(0, x), 0)) + +def rms(x): return np.sqrt(np.mean(x**2)) + +def get_GH_data_identifier(video_name, start_idx, split='_'): + if isinstance(start_idx, str): + return video_name + split + start_idx + elif isinstance(start_idx, int): + return video_name + split + str(start_idx) + else: + raise NotImplementedError + +def draw_spec(spec, dest, cmap='magma'): + plt.imshow(spec, cmap=cmap, origin='lower') + plt.axis('off') + plt.savefig(dest, bbox_inches='tight', pad_inches=0., dpi=300) + plt.close() + +def convert_to_decibel(arr): + ref = 1 + return 20 * np.log10(abs(arr + 1e-4) / ref) + +class ResampleFrames(object): + def __init__(self, feat_sample_size, times_to_repeat_after_resample=None): + self.feat_sample_size = feat_sample_size + self.times_to_repeat_after_resample = times_to_repeat_after_resample + + def __call__(self, item): + feat_len = item['feature'].shape[0] + + ## resample + assert feat_len >= self.feat_sample_size + # evenly spaced points (abcdefghkl -> aoooofoooo) + idx = np.linspace(0, feat_len, self.feat_sample_size, dtype=np.int, endpoint=False) + # xoooo xoooo -> ooxoo ooxoo + shift = feat_len // (self.feat_sample_size + 1) + idx = idx + shift + + ## repeat after resampling (abc -> aaaabbbbcccc) + if self.times_to_repeat_after_resample is not None and self.times_to_repeat_after_resample > 1: + idx = np.repeat(idx, self.times_to_repeat_after_resample) + + item['feature'] = item['feature'][idx, :] + return item + + +class ImpactSetWave(torch.utils.data.Dataset): + + def __init__(self, split, random_crop, mel_num, spec_crop_len, + L=2.0, denoise=False, splits_path='./data', + data_path='data/ImpactSet/impactset-proccess-resize'): + super().__init__() + self.split = split + self.splits_path = splits_path + self.data_path = data_path + self.L = L + self.denoise = denoise + + video_name_split_path = os.path.join(splits_path, f'countixAV_{split}.json') + if not os.path.exists(video_name_split_path): + self.make_split_files() + video_name = json.load(open(video_name_split_path, 'r')) + self.video_frame_cnt = {v: len(os.listdir(os.path.join(self.data_path, v, 'frames'))) for v in video_name} + self.left_over = int(FPS * L + 1) + self.video_audio_path = {v: os.path.join(self.data_path, v, f'audio/{v}_resampled.wav') for v in video_name} + self.dataset = video_name + + self.wav_transforms = transforms.Compose([ + MakeMono(), + Padding(target_len=int(SR * self.L)), + ]) + + self.spec_transforms = CropImage([mel_num, spec_crop_len], random_crop) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + item = {} + video = self.dataset[idx] + + available_frame_idx = self.video_frame_cnt[video] - self.left_over + wav = None + spec = None + max_db = -np.inf + wave_path = '' + cur_wave_path = self.video_audio_path[video] + if self.denoise: + cur_wave_path = cur_wave_path.replace('.wav', '_denoised.wav') + for _ in range(10): + start_idx = torch.randint(0, available_frame_idx, (1,)).tolist()[0] + # target + start_t = (start_idx + 0.5) / FPS + start_audio_idx = non_negative(start_t * SR) + + cur_wav, _ = soundfile.read(cur_wave_path, frames=int(SR * self.L), start=start_audio_idx) + + decibel = convert_to_decibel(cur_wav) + if float(np.mean(decibel)) > max_db: + wav = cur_wav + wave_path = cur_wave_path + max_db = float(np.mean(decibel)) + if max_db >= -40: + break + + # print(max_db) + wav = self.wav_transforms(wav) + item['image'] = wav # (80, 173) + # item['wav'] = wav + item['file_path_wav_'] = wave_path + + item['label'] = 'None' + item['target'] = 'None' + + return item + + def make_split_files(self): + raise NotImplementedError + +class ImpactSetWaveTrain(ImpactSetWave): + def __init__(self, specs_dataset_cfg): + super().__init__('train', **specs_dataset_cfg) + +class ImpactSetWaveValidation(ImpactSetWave): + def __init__(self, specs_dataset_cfg): + super().__init__('val', **specs_dataset_cfg) + +class ImpactSetWaveTest(ImpactSetWave): + def __init__(self, specs_dataset_cfg): + super().__init__('test', **specs_dataset_cfg) + + +class ImpactSetSpec(torch.utils.data.Dataset): + + def __init__(self, split, random_crop, mel_num, spec_crop_len, + L=2.0, denoise=False, splits_path='./data', + data_path='data/ImpactSet/impactset-proccess-resize'): + super().__init__() + self.split = split + self.splits_path = splits_path + self.data_path = data_path + self.L = L + self.denoise = denoise + + video_name_split_path = os.path.join(splits_path, f'countixAV_{split}.json') + if not os.path.exists(video_name_split_path): + self.make_split_files() + video_name = json.load(open(video_name_split_path, 'r')) + self.video_frame_cnt = {v: len(os.listdir(os.path.join(self.data_path, v, 'frames'))) for v in video_name} + self.left_over = int(FPS * L + 1) + self.video_audio_path = {v: os.path.join(self.data_path, v, f'audio/{v}_resampled.wav') for v in video_name} + self.dataset = video_name + + self.wav_transforms = transforms.Compose([ + MakeMono(), + SpectrogramTorchAudio(nfft=1024, hoplen=1024//4, spec_power=1), + MelScaleTorchAudio(sr=SR, stft=513, fmin=125, fmax=7600, nmels=80), + LowerThresh(1e-5), + Log10(), + Multiply(20), + Subtract(20), + Add(100), + Divide(100), + Clip(0, 1.0), + TrimSpec(173), + ]) + + self.spec_transforms = CropImage([mel_num, spec_crop_len], random_crop) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + item = {} + video = self.dataset[idx] + + available_frame_idx = self.video_frame_cnt[video] - self.left_over + wav = None + spec = None + max_rms = -np.inf + wave_path = '' + cur_wave_path = self.video_audio_path[video] + if self.denoise: + cur_wave_path = cur_wave_path.replace('.wav', '_denoised.wav') + for _ in range(10): + start_idx = torch.randint(0, available_frame_idx, (1,)).tolist()[0] + # target + start_t = (start_idx + 0.5) / FPS + start_audio_idx = non_negative(start_t * SR) + + cur_wav, _ = soundfile.read(cur_wave_path, frames=int(SR * self.L), start=start_audio_idx) + + if self.wav_transforms is not None: + spec_tensor = self.wav_transforms(torch.tensor(cur_wav).float()) + cur_spec = spec_tensor.numpy() + # zeros padding if not enough spec t steps + if cur_spec.shape[1] < 173: + pad = np.zeros((80, 173), dtype=cur_spec.dtype) + pad[:, :cur_spec.shape[1]] = cur_spec + cur_spec = pad + rms_val = rms(cur_spec) + if rms_val > max_rms: + wav = cur_wav + spec = cur_spec + wave_path = cur_wave_path + max_rms = rms_val + # print(rms_val) + if max_rms >= 0.1: + break + + item['image'] = 2 * spec - 1 # (80, 173) + # item['wav'] = wav + item['file_path_wav_'] = wave_path + + item['label'] = 'None' + item['target'] = 'None' + + if self.spec_transforms is not None: + item = self.spec_transforms(item) + return item + + def make_split_files(self): + raise NotImplementedError + +class ImpactSetSpecTrain(ImpactSetSpec): + def __init__(self, specs_dataset_cfg): + super().__init__('train', **specs_dataset_cfg) + +class ImpactSetSpecValidation(ImpactSetSpec): + def __init__(self, specs_dataset_cfg): + super().__init__('val', **specs_dataset_cfg) + +class ImpactSetSpecTest(ImpactSetSpec): + def __init__(self, specs_dataset_cfg): + super().__init__('test', **specs_dataset_cfg) + + + +class ImpactSetWaveTestTime(torch.utils.data.Dataset): + + def __init__(self, split, random_crop, mel_num, spec_crop_len, + L=2.0, denoise=False, splits_path='./data', + data_path='data/ImpactSet/impactset-proccess-resize'): + super().__init__() + self.split = split + self.splits_path = splits_path + self.data_path = data_path + self.L = L + self.denoise = denoise + + self.video_list = glob('data/ImpactSet/RawVideos/StockVideo_sound/*.wav') + [ + 'data/ImpactSet/RawVideos/YouTube-impact-ccl/1_ckbCU5aQs/1_ckbCU5aQs_0013_0016_resize.wav', + 'data/ImpactSet/RawVideos/YouTube-impact-ccl/GFmuVBiwz6k/GFmuVBiwz6k_0034_0054_resize.wav', + 'data/ImpactSet/RawVideos/YouTube-impact-ccl/OsPcY316h1M/OsPcY316h1M_0000_0005_resize.wav', + 'data/ImpactSet/RawVideos/YouTube-impact-ccl/SExIpBIBj_k/SExIpBIBj_k_0009_0019_resize.wav', + 'data/ImpactSet/RawVideos/YouTube-impact-ccl/S6TkbV4B4QI/S6TkbV4B4QI_0028_0036_resize.wav', + 'data/ImpactSet/RawVideos/YouTube-impact-ccl/2Ld24pPIn3k/2Ld24pPIn3k_0005_0011_resize.wav', + 'data/ImpactSet/RawVideos/YouTube-impact-ccl/6d1YS7fdBK4/6d1YS7fdBK4_0007_0019_resize.wav', + 'data/ImpactSet/RawVideos/YouTube-impact-ccl/JnBsmJgEkiw/JnBsmJgEkiw_0008_0016_resize.wav', + 'data/ImpactSet/RawVideos/YouTube-impact-ccl/xcUyiXt0gjo/xcUyiXt0gjo_0015_0021_resize.wav', + 'data/ImpactSet/RawVideos/YouTube-impact-ccl/4DRFJnZjpMM/4DRFJnZjpMM_0000_0010_resize.wav' + ] + glob('data/ImpactSet/RawVideos/self_recorded/*_resize.wav') + + self.wav_transforms = transforms.Compose([ + MakeMono(), + SpectrogramTorchAudio(nfft=1024, hoplen=1024//4, spec_power=1), + MelScaleTorchAudio(sr=SR, stft=513, fmin=125, fmax=7600, nmels=80), + LowerThresh(1e-5), + Log10(), + Multiply(20), + Subtract(20), + Add(100), + Divide(100), + Clip(0, 1.0), + TrimSpec(173), + ]) + self.spec_transforms = CropImage([mel_num, spec_crop_len], random_crop) + + def __len__(self): + return len(self.video_list) + + def __getitem__(self, idx): + item = {} + + wave_path = self.video_list[idx] + + wav, _ = soundfile.read(wave_path) + start_idx = random.randint(0, min(4, wav.shape[0] - int(SR * self.L))) + wav = wav[start_idx:start_idx+int(SR * self.L)] + + if self.denoise: + if len(wav.shape) == 1: + wav = wav[None, :] + wav = nr.reduce_noise(y=wav, sr=SR, n_fft=1024, hop_length=1024//4) + wav = wav.squeeze() + if self.wav_transforms is not None: + spec_tensor = self.wav_transforms(torch.tensor(wav).float()) + spec = spec_tensor.numpy() + if spec.shape[1] < 173: + pad = np.zeros((80, 173), dtype=spec.dtype) + pad[:, :spec.shape[1]] = spec + spec = pad + + item['image'] = 2 * spec - 1 # (80, 173) + # item['wav'] = wav + item['file_path_wav_'] = wave_path + + item['label'] = 'None' + item['target'] = 'None' + + if self.spec_transforms is not None: + item = self.spec_transforms(item) + return item + + def make_split_files(self): + raise NotImplementedError + +class ImpactSetWaveTestTimeTrain(ImpactSetWaveTestTime): + def __init__(self, specs_dataset_cfg): + super().__init__('train', **specs_dataset_cfg) + +class ImpactSetWaveTestTimeValidation(ImpactSetWaveTestTime): + def __init__(self, specs_dataset_cfg): + super().__init__('val', **specs_dataset_cfg) + +class ImpactSetWaveTestTimeTest(ImpactSetWaveTestTime): + def __init__(self, specs_dataset_cfg): + super().__init__('test', **specs_dataset_cfg) + + +class ImpactSetWaveWithSilent(torch.utils.data.Dataset): + + def __init__(self, split, random_crop, mel_num, spec_crop_len, + L=2.0, denoise=False, splits_path='./data', + data_path='data/ImpactSet/impactset-proccess-resize'): + super().__init__() + self.split = split + self.splits_path = splits_path + self.data_path = data_path + self.L = L + self.denoise = denoise + + video_name_split_path = os.path.join(splits_path, f'countixAV_{split}.json') + if not os.path.exists(video_name_split_path): + self.make_split_files() + video_name = json.load(open(video_name_split_path, 'r')) + self.video_frame_cnt = {v: len(os.listdir(os.path.join(self.data_path, v, 'frames'))) for v in video_name} + self.left_over = int(FPS * L + 1) + self.video_audio_path = {v: os.path.join(self.data_path, v, f'audio/{v}_resampled.wav') for v in video_name} + self.dataset = video_name + + self.wav_transforms = transforms.Compose([ + MakeMono(), + Padding(target_len=int(SR * self.L)), + ]) + + self.spec_transforms = CropImage([mel_num, spec_crop_len], random_crop) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + item = {} + video = self.dataset[idx] + + available_frame_idx = self.video_frame_cnt[video] - self.left_over + wave_path = self.video_audio_path[video] + if self.denoise: + wave_path = wave_path.replace('.wav', '_denoised.wav') + start_idx = torch.randint(0, available_frame_idx, (1,)).tolist()[0] + # target + start_t = (start_idx + 0.5) / FPS + start_audio_idx = non_negative(start_t * SR) + + wav, _ = soundfile.read(wave_path, frames=int(SR * self.L), start=start_audio_idx) + + wav = self.wav_transforms(wav) + + item['image'] = wav # (44100,) + # item['wav'] = wav + item['file_path_wav_'] = wave_path + + item['label'] = 'None' + item['target'] = 'None' + return item + + def make_split_files(self): + raise NotImplementedError + +class ImpactSetWaveWithSilentTrain(ImpactSetWaveWithSilent): + def __init__(self, specs_dataset_cfg): + super().__init__('train', **specs_dataset_cfg) + +class ImpactSetWaveWithSilentValidation(ImpactSetWaveWithSilent): + def __init__(self, specs_dataset_cfg): + super().__init__('val', **specs_dataset_cfg) + +class ImpactSetWaveWithSilentTest(ImpactSetWaveWithSilent): + def __init__(self, specs_dataset_cfg): + super().__init__('test', **specs_dataset_cfg) + + +class ImpactSetWaveCondOnImage(torch.utils.data.Dataset): + + def __init__(self, split, + L=2.0, frame_transforms=None, denoise=False, splits_path='./data', + data_path='data/ImpactSet/impactset-proccess-resize', + p_outside_cond=0.): + super().__init__() + self.split = split + self.splits_path = splits_path + self.frame_transforms = frame_transforms + self.data_path = data_path + self.L = L + self.denoise = denoise + self.p_outside_cond = torch.tensor(p_outside_cond) + + video_name_split_path = os.path.join(splits_path, f'countixAV_{split}.json') + if not os.path.exists(video_name_split_path): + self.make_split_files() + video_name = json.load(open(video_name_split_path, 'r')) + self.video_frame_cnt = {v: len(os.listdir(os.path.join(self.data_path, v, 'frames'))) for v in video_name} + self.left_over = int(FPS * L + 1) + for v, cnt in self.video_frame_cnt.items(): + if cnt - (3*self.left_over) <= 0: + video_name.remove(v) + self.video_audio_path = {v: os.path.join(self.data_path, v, f'audio/{v}_resampled.wav') for v in video_name} + self.dataset = video_name + + video_timing_split_path = os.path.join(splits_path, f'countixAV_{split}_timing.json') + self.video_timing = json.load(open(video_timing_split_path, 'r')) + self.video_timing = {v: [int(float(t) * FPS) for t in ts] for v, ts in self.video_timing.items()} + + if split != 'test': + video_class_path = os.path.join(splits_path, f'countixAV_{split}_class.json') + if not os.path.exists(video_class_path): + self.make_video_class() + self.video_class = json.load(open(video_class_path, 'r')) + self.class2video = {} + for v, c in self.video_class.items(): + if c not in self.class2video.keys(): + self.class2video[c] = [] + self.class2video[c].append(v) + + self.wav_transforms = transforms.Compose([ + MakeMono(), + Padding(target_len=int(SR * self.L)), + ]) + if self.frame_transforms == None: + self.frame_transforms = transforms.Compose([ + Resize3D(128), + RandomResizedCrop3D(112, scale=(0.5, 1.0)), + RandomHorizontalFlip3D(), + ColorJitter3D(brightness=0.1, saturation=0.1), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + + def make_video_class(self): + meta_path = f'data/ImpactSet/data-info/CountixAV_{self.split}.csv' + video_class = {} + with open(meta_path, 'r') as f: + reader = csv.reader(f) + for i, row in enumerate(reader): + if i == 0: + continue + vid, k_st, k_et = row[:3] + video_name = f'{vid}_{int(k_st):0>4d}_{int(k_et):0>4d}' + if video_name not in self.dataset: + continue + video_class[video_name] = row[-1] + with open(os.path.join(self.splits_path, f'countixAV_{self.split}_class.json'), 'w') as f: + json.dump(video_class, f) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + item = {} + video = self.dataset[idx] + + available_frame_idx = self.video_frame_cnt[video] - self.left_over + rep_start_idx, rep_end_idx = self.video_timing[video] + rep_end_idx = min(available_frame_idx, rep_end_idx) + if available_frame_idx <= rep_start_idx + self.L * FPS: + idx_set = list(range(0, available_frame_idx)) + else: + idx_set = list(range(rep_start_idx, rep_end_idx)) + start_idx = sample(idx_set, k=1)[0] + + wave_path = self.video_audio_path[video] + if self.denoise: + wave_path = wave_path.replace('.wav', '_denoised.wav') + + # target + start_t = (start_idx + 0.5) / FPS + end_idx= non_negative(start_idx + FPS * self.L) + start_audio_idx = non_negative(start_t * SR) + wav, sr = soundfile.read(wave_path, frames=int(SR * self.L), start=start_audio_idx) + assert sr == SR + wav = self.wav_transforms(wav) + frame_path = os.path.join(self.data_path, video, 'frames') + frames = [Image.open(os.path.join( + frame_path, f'frame{i+1:0>6d}.jpg')).convert('RGB') for i in + range(start_idx, end_idx)] + + if torch.all(torch.bernoulli(self.p_outside_cond) == 1.) and self.split != 'test': + # outside from the same class + cur_class = self.video_class[video] + tmp_video = copy.copy(self.class2video[cur_class]) + if len(tmp_video) > 1: + # if only 1 video in the class, use itself + tmp_video.remove(video) + cond_video = sample(tmp_video, k=1)[0] + cond_available_frame_idx = self.video_frame_cnt[cond_video] - self.left_over + cond_start_idx = torch.randint(0, cond_available_frame_idx, (1,)).tolist()[0] + else: + cond_video = video + idx_set = list(range(0, start_idx)) + list(range(end_idx, available_frame_idx)) + cond_start_idx = random.sample(idx_set, k=1)[0] + + cond_end_idx = non_negative(cond_start_idx + FPS * self.L) + cond_start_t = (cond_start_idx + 0.5) / FPS + cond_audio_idx = non_negative(cond_start_t * SR) + cond_frame_path = os.path.join(self.data_path, cond_video, 'frames') + cond_wave_path = self.video_audio_path[cond_video] + + cond_frames = [Image.open(os.path.join( + cond_frame_path, f'frame{i+1:0>6d}.jpg')).convert('RGB') for i in + range(cond_start_idx, cond_end_idx)] + cond_wav, sr = soundfile.read(cond_wave_path, frames=int(SR * self.L), start=cond_audio_idx) + assert sr == SR + cond_wav = self.wav_transforms(cond_wav) + + item['image'] = wav # (44100,) + item['cond_image'] = cond_wav # (44100,) + item['file_path_wav_'] = wave_path + item['file_path_cond_wav_'] = cond_wave_path + + if self.frame_transforms is not None: + cond_frames = self.frame_transforms(cond_frames) + frames = self.frame_transforms(frames) + + item['feature'] = np.stack(cond_frames + frames, axis=0) # (30 * L, 112, 112, 3) + item['file_path_feats_'] = (frame_path, start_idx) + item['file_path_cond_feats_'] = (cond_frame_path, cond_start_idx) + + item['label'] = 'None' + item['target'] = 'None' + + return item + + def make_split_files(self): + raise NotImplementedError + + +class ImpactSetWaveCondOnImageTrain(ImpactSetWaveCondOnImage): + def __init__(self, dataset_cfg): + train_transforms = transforms.Compose([ + Resize3D(128), + RandomResizedCrop3D(112, scale=(0.5, 1.0)), + RandomHorizontalFlip3D(), + ColorJitter3D(brightness=0.4, saturation=0.4, contrast=0.2, hue=0.1), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('train', frame_transforms=train_transforms, **dataset_cfg) + +class ImpactSetWaveCondOnImageValidation(ImpactSetWaveCondOnImage): + def __init__(self, dataset_cfg): + valid_transforms = transforms.Compose([ + Resize3D(128), + CenterCrop3D(112), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('val', frame_transforms=valid_transforms, **dataset_cfg) + +class ImpactSetWaveCondOnImageTest(ImpactSetWaveCondOnImage): + def __init__(self, dataset_cfg): + test_transforms = transforms.Compose([ + Resize3D(128), + CenterCrop3D(112), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('test', frame_transforms=test_transforms, **dataset_cfg) + + + +class ImpactSetCleanWaveCondOnImage(ImpactSetWaveCondOnImage): + def __init__(self, split, L=2, frame_transforms=None, denoise=False, splits_path='./data', data_path='data/ImpactSet/impactset-proccess-resize', p_outside_cond=0): + super().__init__(split, L, frame_transforms, denoise, splits_path, data_path, p_outside_cond) + pred_timing_path = f'data/countixAV_{split}_timing_processed_0.20.json' + assert os.path.exists(pred_timing_path) + self.pred_timing = json.load(open(pred_timing_path, 'r')) + + self.dataset = [] + for v, ts in self.pred_timing.items(): + if v in self.video_audio_path.keys(): + for t in ts: + self.dataset.append([v, t]) + + def __getitem__(self, idx): + item = {} + video, start_t = self.dataset[idx] + available_frame_idx = self.video_frame_cnt[video] - self.left_over + available_timing = (available_frame_idx + 0.5) / FPS + start_t = float(start_t) + start_t = min(start_t, available_timing) + + start_idx = non_negative(start_t * FPS - 0.5) + + wave_path = self.video_audio_path[video] + if self.denoise: + wave_path = wave_path.replace('.wav', '_denoised.wav') + + # target + end_idx= non_negative(start_idx + FPS * self.L) + start_audio_idx = non_negative(start_t * SR) + wav, sr = soundfile.read(wave_path, frames=int(SR * self.L), start=start_audio_idx) + assert sr == SR + wav = self.wav_transforms(wav) + frame_path = os.path.join(self.data_path, video, 'frames') + frames = [Image.open(os.path.join( + frame_path, f'frame{i+1:0>6d}.jpg')).convert('RGB') for i in + range(start_idx, end_idx)] + + if torch.all(torch.bernoulli(self.p_outside_cond) == 1.): + other_video = list(self.pred_timing.keys()) + other_video.remove(video) + cond_video = sample(other_video, k=1)[0] + cond_available_frame_idx = self.video_frame_cnt[cond_video] - self.left_over + cond_available_timing = (cond_available_frame_idx + 0.5) / FPS + else: + cond_video = video + cond_available_timing = available_timing + + cond_start_t = sample(self.pred_timing[cond_video], k=1)[0] + cond_start_t = float(cond_start_t) + cond_start_t = min(cond_start_t, cond_available_timing) + cond_start_idx = non_negative(cond_start_t * FPS - 0.5) + cond_end_idx = non_negative(cond_start_idx + FPS * self.L) + cond_audio_idx = non_negative(cond_start_t * SR) + cond_frame_path = os.path.join(self.data_path, cond_video, 'frames') + cond_wave_path = self.video_audio_path[cond_video] + + cond_frames = [Image.open(os.path.join( + cond_frame_path, f'frame{i+1:0>6d}.jpg')).convert('RGB') for i in + range(cond_start_idx, cond_end_idx)] + cond_wav, sr = soundfile.read(cond_wave_path, frames=int(SR * self.L), start=cond_audio_idx) + assert sr == SR + cond_wav = self.wav_transforms(cond_wav) + + item['image'] = wav # (44100,) + item['cond_image'] = cond_wav # (44100,) + item['file_path_wav_'] = wave_path + item['file_path_cond_wav_'] = cond_wave_path + + if self.frame_transforms is not None: + cond_frames = self.frame_transforms(cond_frames) + frames = self.frame_transforms(frames) + + item['feature'] = np.stack(cond_frames + frames, axis=0) # (30 * L, 112, 112, 3) + item['file_path_feats_'] = (frame_path, start_idx) + item['file_path_cond_feats_'] = (cond_frame_path, cond_start_idx) + + item['label'] = 'None' + item['target'] = 'None' + + return item + + +class ImpactSetCleanWaveCondOnImageTrain(ImpactSetCleanWaveCondOnImage): + def __init__(self, dataset_cfg): + train_transforms = transforms.Compose([ + Resize3D(128), + RandomResizedCrop3D(112, scale=(0.5, 1.0)), + RandomHorizontalFlip3D(), + ColorJitter3D(brightness=0.4, saturation=0.4, contrast=0.2, hue=0.1), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('train', frame_transforms=train_transforms, **dataset_cfg) + +class ImpactSetCleanWaveCondOnImageValidation(ImpactSetCleanWaveCondOnImage): + def __init__(self, dataset_cfg): + valid_transforms = transforms.Compose([ + Resize3D(128), + CenterCrop3D(112), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('val', frame_transforms=valid_transforms, **dataset_cfg) + +class ImpactSetCleanWaveCondOnImageTest(ImpactSetCleanWaveCondOnImage): + def __init__(self, dataset_cfg): + test_transforms = transforms.Compose([ + Resize3D(128), + CenterCrop3D(112), + ToTensor3D(), + Normalize3D(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + ]) + super().__init__('test', frame_transforms=test_transforms, **dataset_cfg) + + +if __name__ == '__main__': + import sys + + from omegaconf import OmegaConf + cfg = OmegaConf.load('configs/countixAV_transformer_denoise_clean.yaml') + data = instantiate_from_config(cfg.data) + data.prepare_data() + data.setup() + + print(data.datasets['train']) + print(len(data.datasets['train'])) + # print(data.datasets['train'][24]) + exit() + + stats = [] + torch.manual_seed(0) + np.random.seed(0) + random.seed = 0 + for k in range(1): + x = np.arange(SR * 2) + for i in tqdm(range(len(data.datasets['train']))): + wav = data.datasets['train'][i]['wav'] + spec = data.datasets['train'][i]['image'] + spec = 0.5 * (spec + 1) + spec_rms = rms(spec) + stats.append(float(spec_rms)) + # plt.plot(x, wav) + # plt.ylim(-1, 1) + # plt.savefig(f'tmp/th0.1_wav_e_{k}_{i}_{mean_val:.3f}_{spec_rms:.3f}.png') + # plt.close() + # plt.cla() + soundfile.write(f'tmp/wav_e_{k}_{i}_{spec_rms:.3f}.wav', wav, SR) + draw_spec(spec, f'tmp/wav_spec_e_{k}_{i}_{spec_rms:.3f}.png') + if i == 100: + break + # plt.hist(stats, bins=50) + # plt.savefig(f'tmp/rms_spec_stats.png') diff --git a/foleycrafter/models/specvqgan/data/transforms.py b/foleycrafter/models/specvqgan/data/transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..a2b5e022b1f4c3ae4bc62dc0e88240c919417f23 --- /dev/null +++ b/foleycrafter/models/specvqgan/data/transforms.py @@ -0,0 +1,685 @@ +import torch +import torchaudio +import torchaudio.functional +from torchvision import transforms +import torchvision.transforms.functional as F +import torch.nn as nn +from PIL import Image +import numpy as np +import math +import random +import soundfile +import os +import librosa +import albumentations +from torch_pitch_shift import * + +SR = 22050 + +class ResizeShortSide(object): + def __init__(self, size): + super().__init__() + self.size = size + + def __call__(self, x): + ''' + x must be PIL.Image + ''' + w, h = x.size + short_side = min(w, h) + w_target = int((w / short_side) * self.size) + h_target = int((h / short_side) * self.size) + return x.resize((w_target, h_target)) + + +class Crop(object): + def __init__(self, cropped_shape=None, random_crop=False): + self.cropped_shape = cropped_shape + if cropped_shape is not None: + mel_num, spec_len = cropped_shape + if random_crop: + self.cropper = albumentations.RandomCrop + else: + self.cropper = albumentations.CenterCrop + self.preprocessor = albumentations.Compose([self.cropper(mel_num, spec_len)]) + else: + self.preprocessor = lambda **kwargs: kwargs + + def __call__(self, item): + item['image'] = self.preprocessor(image=item['image'])['image'] + if 'cond_image' in item.keys(): + item['cond_image'] = self.preprocessor(image=item['cond_image'])['image'] + return item + +class CropImage(Crop): + def __init__(self, *crop_args): + super().__init__(*crop_args) + +class CropFeats(Crop): + def __init__(self, *crop_args): + super().__init__(*crop_args) + + def __call__(self, item): + item['feature'] = self.preprocessor(image=item['feature'])['image'] + return item + +class CropCoords(Crop): + def __init__(self, *crop_args): + super().__init__(*crop_args) + + def __call__(self, item): + item['coord'] = self.preprocessor(image=item['coord'])['image'] + return item + + +class RandomResizedCrop3D(nn.Module): + """Crop the given series of images to random size and aspect ratio. + The image can be a PIL Images or a Tensor, in which case it is expected + to have [N, ..., H, W] shape, where ... means an arbitrary number of leading dimensions + + A crop of random size (default: of 0.08 to 1.0) of the original size and a random + aspect ratio (default: of 3/4 to 4/3) of the original aspect ratio is made. This crop + is finally resized to given size. + This is popularly used to train the Inception networks. + + Args: + size (int or sequence): expected output size of each edge. If size is an + int instead of sequence like (h, w), a square output size ``(size, size)`` is + made. If provided a tuple or list of length 1, it will be interpreted as (size[0], size[0]). + scale (tuple of float): range of size of the origin size cropped + ratio (tuple of float): range of aspect ratio of the origin aspect ratio cropped. + interpolation (int): Desired interpolation enum defined by `filters`_. + Default is ``PIL.Image.BILINEAR``. If input is Tensor, only ``PIL.Image.NEAREST``, ``PIL.Image.BILINEAR`` + and ``PIL.Image.BICUBIC`` are supported. + """ + + def __init__(self, size, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.), interpolation=transforms.InterpolationMode.BILINEAR): + super().__init__() + if isinstance(size, tuple) and len(size) == 2: + self.size = size + else: + self.size = (size, size) + + self.interpolation = interpolation + self.scale = scale + self.ratio = ratio + + @staticmethod + def get_params(img, scale, ratio): + """Get parameters for ``crop`` for a random sized crop. + + Args: + img (PIL Image or Tensor): Input image. + scale (list): range of scale of the origin size cropped + ratio (list): range of aspect ratio of the origin aspect ratio cropped + + Returns: + tuple: params (i, j, h, w) to be passed to ``crop`` for a random + sized crop. + """ + width, height = img.size + area = height * width + + for _ in range(10): + target_area = area * \ + torch.empty(1).uniform_(scale[0], scale[1]).item() + log_ratio = torch.log(torch.tensor(ratio)) + aspect_ratio = torch.exp( + torch.empty(1).uniform_(log_ratio[0], log_ratio[1]) + ).item() + + w = int(round(math.sqrt(target_area * aspect_ratio))) + h = int(round(math.sqrt(target_area / aspect_ratio))) + + if 0 < w <= width and 0 < h <= height: + i = torch.randint(0, height - h + 1, size=(1,)).item() + j = torch.randint(0, width - w + 1, size=(1,)).item() + return i, j, h, w + + # Fallback to central crop + in_ratio = float(width) / float(height) + if in_ratio < min(ratio): + w = width + h = int(round(w / min(ratio))) + elif in_ratio > max(ratio): + h = height + w = int(round(h * max(ratio))) + else: # whole image + w = width + h = height + i = (height - h) // 2 + j = (width - w) // 2 + return i, j, h, w + + def forward(self, imgs): + """ + Args: + img (PIL Image or Tensor): Image to be cropped and resized. + + Returns: + PIL Image or Tensor: Randomly cropped and resized image. + """ + i, j, h, w = self.get_params(imgs[0], self.scale, self.ratio) + return [F.resized_crop(img, i, j, h, w, self.size, self.interpolation) for img in imgs] + + +class Resize3D(object): + def __init__(self, size): + super().__init__() + self.size = size + + def __call__(self, imgs): + ''' + x must be PIL.Image + ''' + return [x.resize((self.size, self.size)) for x in imgs] + + +class RandomHorizontalFlip3D(object): + def __init__(self, p=0.5): + super().__init__() + self.p = p + + def __call__(self, imgs): + ''' + x must be PIL.Image + ''' + if np.random.rand() < self.p: + return [x.transpose(Image.FLIP_LEFT_RIGHT) for x in imgs] + else: + return imgs + + +class ColorJitter3D(torch.nn.Module): + """Randomly change the brightness, contrast and saturation of an image. + + Args: + brightness (float or tuple of float (min, max)): How much to jitter brightness. + brightness_factor is chosen uniformly from [max(0, 1 - brightness), 1 + brightness] + or the given [min, max]. Should be non negative numbers. + contrast (float or tuple of float (min, max)): How much to jitter contrast. + contrast_factor is chosen uniformly from [max(0, 1 - contrast), 1 + contrast] + or the given [min, max]. Should be non negative numbers. + saturation (float or tuple of float (min, max)): How much to jitter saturation. + saturation_factor is chosen uniformly from [max(0, 1 - saturation), 1 + saturation] + or the given [min, max]. Should be non negative numbers. + hue (float or tuple of float (min, max)): How much to jitter hue. + hue_factor is chosen uniformly from [-hue, hue] or the given [min, max]. + Should have 0<= hue <= 0.5 or -0.5 <= min <= max <= 0.5. + """ + + def __init__(self, brightness=0, contrast=0, saturation=0, hue=0): + super().__init__() + self.brightness = (1-brightness, 1+brightness) + self.contrast = (1-contrast, 1+contrast) + self.saturation = (1-saturation, 1+saturation) + self.hue = (0-hue, 0+hue) + + @staticmethod + def get_params(brightness, contrast, saturation, hue): + """Get a randomized transform to be applied on image. + + Arguments are same as that of __init__. + + Returns: + Transform which randomly adjusts brightness, contrast and + saturation in a random order. + """ + tfs = [] + + if brightness is not None: + brightness_factor = random.uniform(brightness[0], brightness[1]) + tfs.append(transforms.Lambda( + lambda img: F.adjust_brightness(img, brightness_factor))) + + if contrast is not None: + contrast_factor = random.uniform(contrast[0], contrast[1]) + tfs.append(transforms.Lambda( + lambda img: F.adjust_contrast(img, contrast_factor))) + + if saturation is not None: + saturation_factor = random.uniform(saturation[0], saturation[1]) + tfs.append(transforms.Lambda( + lambda img: F.adjust_saturation(img, saturation_factor))) + + if hue is not None: + hue_factor = random.uniform(hue[0], hue[1]) + tfs.append(transforms.Lambda( + lambda img: F.adjust_hue(img, hue_factor))) + + random.shuffle(tfs) + transform = transforms.Compose(tfs) + + return transform + + def forward(self, imgs): + """ + Args: + img (PIL Image or Tensor): Input image. + + Returns: + PIL Image or Tensor: Color jittered image. + """ + transform = self.get_params( + self.brightness, self.contrast, self.saturation, self.hue) + return [transform(img) for img in imgs] + + +class ToTensor3D(object): + def __init__(self): + super().__init__() + + def __call__(self, imgs): + ''' + x must be PIL.Image + ''' + return [F.to_tensor(img) for img in imgs] + + +class Normalize3D(object): + def __init__(self, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], inplace=False): + super().__init__() + self.mean = mean + self.std = std + self.inplace = inplace + + def __call__(self, imgs): + ''' + x must be PIL.Image + ''' + return [F.normalize(img, self.mean, self.std, self.inplace) for img in imgs] + + +class CenterCrop3D(object): + def __init__(self, size): + super().__init__() + self.size = size + + def __call__(self, imgs): + ''' + x must be PIL.Image + ''' + return [F.center_crop(img, self.size) for img in imgs] + + +class FrequencyMasking(object): + def __init__(self, freq_mask_param: int, iid_masks: bool = False): + super().__init__() + self.masking = torchaudio.transforms.FrequencyMasking(freq_mask_param, iid_masks) + + def __call__(self, item): + if 'cond_image' in item.keys(): + batched_spec = torch.stack( + [torch.tensor(item['image']), torch.tensor(item['cond_image'])], dim=0 + )[:, None] # (2, 1, H, W) + masked = self.masking(batched_spec).numpy() + item['image'] = masked[0, 0] + item['cond_image'] = masked[1, 0] + elif 'image' in item.keys(): + inp = torch.tensor(item['image']) + item['image'] = self.masking(inp).numpy() + else: + raise NotImplementedError() + return item + + +class TimeMasking(object): + def __init__(self, time_mask_param: int, iid_masks: bool = False): + super().__init__() + self.masking = torchaudio.transforms.TimeMasking(time_mask_param, iid_masks) + + def __call__(self, item): + if 'cond_image' in item.keys(): + batched_spec = torch.stack( + [torch.tensor(item['image']), torch.tensor(item['cond_image'])], dim=0 + )[:, None] # (2, 1, H, W) + masked = self.masking(batched_spec).numpy() + item['image'] = masked[0, 0] + item['cond_image'] = masked[1, 0] + elif 'image' in item.keys(): + inp = torch.tensor(item['image']) + item['image'] = self.masking(inp).numpy() + else: + raise NotImplementedError() + return item + + +class PitchShift(nn.Module): + + def __init__(self, up=12, down=-12, sample_rate=SR): + super().__init__() + self.range = (down, up) + self.sr = sample_rate + + def forward(self, x): + assert len(x.shape) == 2 + x = x[:, None, :] + ratio = float(random.randint(self.range[0], self.range[1]) / 12.) + shifted = pitch_shift(x, ratio, self.sr) + return shifted.squeeze() + + +class MelSpectrogram(object): + def __init__(self, sr, nfft, fmin, fmax, nmels, hoplen, spec_power, inverse=False): + self.sr = sr + self.nfft = nfft + self.fmin = fmin + self.fmax = fmax + self.nmels = nmels + self.hoplen = hoplen + self.spec_power = spec_power + self.inverse = inverse + + self.mel_basis = librosa.filters.mel(sr=sr, n_fft=nfft, fmin=fmin, fmax=fmax, n_mels=nmels) + + def __call__(self, x): + x = x.numpy() + if self.inverse: + spec = librosa.feature.inverse.mel_to_stft( + x, sr=self.sr, n_fft=self.nfft, fmin=self.fmin, fmax=self.fmax, power=self.spec_power + ) + wav = librosa.griffinlim(spec, hop_length=self.hoplen) + return torch.FloatTensor(wav) + else: + spec = np.abs(librosa.stft(x, n_fft=self.nfft, hop_length=self.hoplen)) ** self.spec_power + mel_spec = np.dot(self.mel_basis, spec) + return torch.FloatTensor(mel_spec) + +class SpectrogramTorchAudio(object): + def __init__(self, nfft, hoplen, spec_power, inverse=False): + self.nfft = nfft + self.hoplen = hoplen + self.spec_power = spec_power + self.inverse = inverse + + self.spec_trans = torchaudio.transforms.Spectrogram( + n_fft=self.nfft, + hop_length=self.hoplen, + power=self.spec_power, + ) + self.inv_spec_trans = torchaudio.transforms.GriffinLim( + n_fft=self.nfft, + hop_length=self.hoplen, + power=self.spec_power, + ) + + def __call__(self, x): + if self.inverse: + wav = self.inv_spec_trans(x) + return wav + else: + spec = torch.abs(self.spec_trans(x)) + return spec + + +class MelScaleTorchAudio(object): + def __init__(self, sr, stft, fmin, fmax, nmels, inverse=False): + self.sr = sr + self.stft = stft + self.fmin = fmin + self.fmax = fmax + self.nmels = nmels + self.inverse = inverse + + self.mel_trans = torchaudio.transforms.MelScale( + n_mels=self.nmels, + sample_rate=self.sr, + f_min=self.fmin, + f_max=self.fmax, + n_stft=self.stft, + norm='slaney' + ) + self.inv_mel_trans = torchaudio.transforms.InverseMelScale( + n_mels=self.nmels, + sample_rate=self.sr, + f_min=self.fmin, + f_max=self.fmax, + n_stft=self.stft, + norm='slaney' + ) + + def __call__(self, x): + if self.inverse: + spec = self.inv_mel_trans(x) + return spec + else: + mel_spec = self.mel_trans(x) + return mel_spec + +class Padding(object): + def __init__(self, target_len, inverse=False): + self.target_len=int(target_len) + self.inverse = inverse + + def __call__(self, x): + if self.inverse: + return x + else: + x = x.squeeze() + if x.shape[0] < self.target_len: + pad = torch.zeros((self.target_len,), dtype=x.dtype, device=x.device) + pad[:x.shape[0]] = x + x = pad + elif x.shape[0] > self.target_len: + raise NotImplementedError() + return x + +class MakeMono(object): + def __init__(self, inverse=False): + self.inverse = inverse + + def __call__(self, x): + if self.inverse: + return x + else: + x = x.squeeze() + if len(x.shape) == 1: + return torch.FloatTensor(x) + elif len(x.shape) == 2: + target_dim = int(torch.argmin(torch.tensor(x.shape))) + return torch.mean(x, dim=target_dim) + else: + raise NotImplementedError + +class LowerThresh(object): + def __init__(self, min_val, inverse=False): + self.min_val = torch.tensor(min_val) + self.inverse = inverse + + def __call__(self, x): + if self.inverse: + return x + else: + return torch.maximum(self.min_val, x) + +class Add(object): + def __init__(self, val, inverse=False): + self.inverse = inverse + self.val = val + + def __call__(self, x): + if self.inverse: + return x - self.val + else: + return x + self.val + +class Subtract(Add): + def __init__(self, val, inverse=False): + self.inverse = inverse + self.val = val + + def __call__(self, x): + if self.inverse: + return x + self.val + else: + return x - self.val + +class Multiply(object): + def __init__(self, val, inverse=False) -> None: + self.val = val + self.inverse = inverse + + def __call__(self, x): + if self.inverse: + return x / self.val + else: + return x * self.val + +class Divide(Multiply): + def __init__(self, val, inverse=False): + self.inverse = inverse + self.val = val + + def __call__(self, x): + if self.inverse: + return x * self.val + else: + return x / self.val + + +class Log10(object): + def __init__(self, inverse=False): + self.inverse = inverse + + def __call__(self, x): + if self.inverse: + return 10 ** x + else: + return torch.log10(x) + +class Clip(object): + def __init__(self, min_val, max_val, inverse=False): + self.min_val = min_val + self.max_val = max_val + self.inverse = inverse + + def __call__(self, x): + if self.inverse: + return x + else: + return torch.clip(x, self.min_val, self.max_val) + +class TrimSpec(object): + def __init__(self, max_len, inverse=False): + self.max_len = max_len + self.inverse = inverse + + def __call__(self, x): + if self.inverse: + return x + else: + return x[:, :self.max_len] + +class MaxNorm(object): + def __init__(self, inverse=False): + self.inverse = inverse + self.eps = 1e-10 + + def __call__(self, x): + if self.inverse: + return x + else: + return x / (x.max() + self.eps) + + +class NormalizeAudio(object): + def __init__(self, inverse=False, desired_rms=0.1, eps=1e-4): + self.inverse = inverse + self.desired_rms = desired_rms + self.eps = torch.tensor(eps) + + def __call__(self, x): + if self.inverse: + return x + else: + rms = torch.maximum(self.eps, torch.sqrt(torch.mean(x**2))) + x = x * (self.desired_rms / rms) + x[x > 1.] = 1. + x[x < -1.] = -1. + return x + + +class RandomNormalizeAudio(object): + def __init__(self, inverse=False, rms_range=[0.05, 0.2], eps=1e-4): + self.inverse = inverse + self.rms_low, self.rms_high = rms_range + self.eps = torch.tensor(eps) + + def __call__(self, x): + if self.inverse: + return x + else: + rms = torch.maximum(self.eps, torch.sqrt(torch.mean(x**2))) + desired_rms = (torch.rand(1) * (self.rms_high - self.rms_low)) + self.rms_low + x = x * (desired_rms / rms) + x[x > 1.] = 1. + x[x < -1.] = -1. + return x + + +class MakeDouble(nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x): + return x.to(torch.double) + + +class MakeFloat(nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x): + return x.to(torch.float) + + +class Wave2Spectrogram(nn.Module): + def __init__(self, mel_num, spec_crop_len): + super().__init__() + self.trans = transforms.Compose([ + LowerThresh(1e-5), + Log10(), + Multiply(20), + Subtract(20), + Add(100), + Divide(100), + Clip(0, 1.0), + TrimSpec(173), + transforms.CenterCrop((mel_num, spec_crop_len)) + ]) + + def forward(self, x): + return self.trans(x) + + + +TRANSFORMS = transforms.Compose([ + SpectrogramTorchAudio(nfft=1024, hoplen=1024//4, spec_power=1), + MelScaleTorchAudio(sr=22050, stft=513, fmin=125, fmax=7600, nmels=80), + LowerThresh(1e-5), + Log10(), + Multiply(20), + Subtract(20), + Add(100), + Divide(100), + Clip(0, 1.0), +]) + +def get_spectrogram_torch(audio_path, save_dir, length, save_results=True): + wav, _ = soundfile.read(audio_path) + wav = torch.FloatTensor(wav) + y = torch.zeros(length) + if wav.shape[0] < length: + y[:len(wav)] = wav + else: + y = wav[:length] + + mel_spec = TRANSFORMS(y).numpy() + y = y.numpy() + if save_results: + os.makedirs(save_dir, exist_ok=True) + audio_name = os.path.basename(audio_path).split('.')[0] + np.save(os.path.join(save_dir, audio_name + '_mel.npy'), mel_spec) + np.save(os.path.join(save_dir, audio_name + '_audio.npy'), y) + else: + return y, mel_spec diff --git a/foleycrafter/models/specvqgan/data/utils.py b/foleycrafter/models/specvqgan/data/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..4e1f221f3415bf66a376e23aef7c9905181f6557 --- /dev/null +++ b/foleycrafter/models/specvqgan/data/utils.py @@ -0,0 +1,265 @@ +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +import numpy as np +import json +from random import shuffle, choice, sample + +from moviepy.editor import VideoFileClip +import librosa +from scipy import signal +from scipy.io import wavfile +import torchaudio +torchaudio.set_audio_backend("sox_io") + +INTERVAL = 1000 + +# discard +stft = torchaudio.transforms.MelSpectrogram( + sample_rate=16000, hop_length=161, n_mels=64).cuda() + + +def log10(x): return torch.log(x)/torch.log(torch.tensor(10.)) + + +def norm_range(x, min_val, max_val): + return 2.*(x - min_val)/float(max_val - min_val) - 1. + + +def normalize_spec(spec, spec_min, spec_max): + return norm_range(spec, spec_min, spec_max) + + +def db_from_amp(x, cuda=False): + # rescale the audio + if cuda: + return 20. * log10(torch.max(torch.tensor(1e-5).to('cuda'), x.float())) + else: + return 20. * log10(torch.max(torch.tensor(1e-5), x.float())) + + +def audio_stft(audio, stft=stft): + # We'll apply stft to the audio samples to convert it to a HxW matrix + N, C, A = audio.size() + audio = audio.view(N * C, A) + spec = stft(audio) + spec = spec.transpose(-1, -2) + spec = db_from_amp(spec, cuda=True) + spec = normalize_spec(spec, -100., 100.) + _, T, F = spec.size() + spec = spec.view(N, C, T, F) + return spec + + +# discard +# def get_spec( +# wavs, +# sample_rate=16000, +# use_volume_jittering=False, +# center=False, +# ): +# # Volume jittering - scale volume by factor in range (0.9, 1.1) +# if use_volume_jittering: +# wavs = [wav * np.random.uniform(0.9, 1.1) for wav in wavs] +# if center: +# wavs = [center_only(wav) for wav in wavs] + +# # Convert to log filterbank +# specs = [logfbank( +# wav, +# sample_rate, +# winlen=0.009, +# winstep=0.005, # if num_sec==1 else 0.01, +# nfilt=256, +# nfft=1024 +# ).astype('float32').T for wav in wavs] + +# # Convert to 32-bit float and expand dim +# specs = np.stack(specs, axis=0) +# specs = np.expand_dims(specs, 1) +# specs = torch.as_tensor(specs) # Nx1xFxT + +# return specs + + +def center_only(audio, sr=16000, L=1.0): + # center_wav = np.arange(0, L, L/(0.5*sr)) ** 2 + # center_wav = np.concatenate([center_wav, center_wav[::-1]]) + # center_wav[L*sr//2:3*L*sr//4] = 1 + # only take 0.3 sec audio + center_wav = np.zeros(int(L * sr)) + center_wav[int(0.4*L*sr):int(0.7*L*sr)] = 1 + + return audio * center_wav + +def get_spec_librosa( + wavs, + sample_rate=16000, + use_volume_jittering=False, + center=False, +): + # Volume jittering - scale volume by factor in range (0.9, 1.1) + if use_volume_jittering: + wavs = [wav * np.random.uniform(0.9, 1.1) for wav in wavs] + if center: + wavs = [center_only(wav) for wav in wavs] + + # Convert to log filterbank + specs = [librosa.feature.melspectrogram( + y=wav, + sr=sample_rate, + n_fft=400, + hop_length=126, + n_mels=128, + ).astype('float32') for wav in wavs] + + # Convert to 32-bit float and expand dim + specs = [librosa.power_to_db(spec) for spec in specs] + specs = np.stack(specs, axis=0) + specs = np.expand_dims(specs, 1) + specs = torch.as_tensor(specs) # Nx1xFxT + + return specs + + +def calcEuclideanDistance_Mat(X, Y): + """ + Inputs: + - X: A numpy array of shape (N, F) + - Y: A numpy array of shape (M, F) + + Returns: + A numpy array D of shape (N, M) where D[i, j] is the Euclidean distance + between X[i] and Y[j]. + """ + return ((torch.sum(X ** 2, axis=1, keepdims=True)) + (torch.sum(Y ** 2, axis=1, keepdims=True)).T - 2 * X @ Y.T) ** 0.5 + + +def calcEuclideanDistance(x1, x2): + return torch.sum((x1 - x2)**2, dim=1)**0.5 + + +def split_data(in_list, portion=(0.9, 0.95), is_shuffle=True): + if is_shuffle: + shuffle(in_list) + if type(in_list) == str: + with open(in_list) as l: + fw_list = json.load(l) + elif type(in_list) == list: + fw_list = in_list + else: + print(type(in_list)) + raise TypeError('Invalid input list type') + c1, c2 = int(len(fw_list) * portion[0]), int(len(fw_list) * portion[1]) + tr_list, va_list, te_list = fw_list[:c1], fw_list[c1:c2], fw_list[c2:] + print( + f'==> train set: {len(tr_list)}, validation set: {len(va_list)}, test set: {len(te_list)}') + return tr_list, va_list, te_list + + +def load_one_clip(video_path): + v = VideoFileClip(video_path) + fps = int(v.fps) + frames = [f for f in v.iter_frames()][:-1] + frame_cnt = len(frames) + frame_length = 1000./fps + total_length = int(1000 * (frame_cnt / fps)) + + a = v.audio + sr = a.fps + a = np.array([fa for fa in a.iter_frames()]) + a = librosa.resample(a, sr, 48000) + if len(a.shape) > 1: + a = np.mean(a, axis=1) + + while True: + idx = np.random.choice(np.arange(frame_cnt - 1), 1)[0] + frame_clip = frames[idx] + start_time = int(idx * frame_length + 0.5 * frame_length - 500) + end_time = start_time + INTERVAL + if start_time < 0 or end_time > total_length: + continue + wave_clip = a[48 * start_time: 48 * end_time] + if wave_clip.shape[0] != 48000: + continue + break + return frame_clip, wave_clip + + +def resize_frame(frame): + H, W = frame.size + short_edge = min(H, W) + scale = 256 / short_edge + H_tar, W_tar = int(np.round(H * scale)), int(np.round(W * scale)) + return frame.resize((H_tar, W_tar)) + + +def get_spectrogram(wave, amp_jitter, amp_jitter_range, log_scale=True, sr=48000): + # random clip-level amplitude jittering + if amp_jitter: + amplified = wave * np.random.uniform(*amp_jitter_range) + if wave.dtype == np.int16: + amplified[amplified >= 32767] = 32767 + amplified[amplified <= -32768] = -32768 + wave = amplified.astype('int16') + elif wave.dtype == np.float32 or wave.dtype == np.float64: + amplified[amplified >= 1] = 1 + amplified[amplified <= -1] = -1 + + # fr, ts, spectrogram = signal.spectrogram(wave[:48000], fs=sr, nperseg=480, noverlap=240, nfft=512) + # spectrogram = librosa.feature.melspectrogram(S=spectrogram, n_mels=257) # Try log-mel spectrogram? + spectrogram = librosa.feature.melspectrogram( + y=wave[:48000], sr=sr, hop_length=240, win_length=480, n_mels=257) + if log_scale: + spectrogram = librosa.power_to_db(spectrogram, ref=np.max) + assert spectrogram.shape[0] == 257 + + return spectrogram + + +def cropAudio(audio, sr, f_idx, fps=10, length=1., left_shift=0): + time_per_frame = 1./fps + assert audio.shape[0] > sr * length + start_time = f_idx * time_per_frame - left_shift + start_time = 0 if start_time < 0 else start_time + start_idx = int(np.round(sr * start_time)) + end_idx = int(np.round(start_idx + (sr * length))) + if end_idx > audio.shape[0]: + end_idx = audio.shape[0] + start_idx = int(end_idx - (sr * length)) + try: + assert audio[start_idx:end_idx].shape[0] == sr * length + except: + print(audio.shape, start_idx, end_idx, end_idx - start_idx) + exit(1) + return audio[start_idx:end_idx] + + +def pick_async_frame_idx(idx, total_frames, fps=10, gap=2.0, length=1.0, cnt=1): + assert idx < total_frames - fps * length + lower_bound = idx - int((length + gap) * fps) + upper_bound = idx + int((length + gap) * fps) + proposal = list(range(0, lower_bound)) + \ + list(range(upper_bound, int(total_frames - fps * length))) + # assert len(proposal) >= cnt + avail_cnt = len(proposal) + try: + for i in range(cnt - avail_cnt): + proposal.append(proposal[i % avail_cnt]) + except Exception as e: + print(idx, total_frames, proposal) + raise e + return sample(proposal, k=cnt) + + +def adjust_learning_rate(optimizer, epoch, args): + """Decay the learning rate based on schedule""" + lr = args.lr + if args.cos: # cosine lr schedule + lr *= 0.5 * (1. + math.cos(math.pi * epoch / args.epoch)) + else: # stepwise lr schedule + for milestone in args.schedule: + lr *= 0.1 if epoch >= milestone else 1. + for param_group in optimizer.param_groups: + param_group['lr'] = lr diff --git a/foleycrafter/models/specvqgan/models/av_cond_transformer.py b/foleycrafter/models/specvqgan/models/av_cond_transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..feb67b0a33456e4157822329a04d857dc61975e5 --- /dev/null +++ b/foleycrafter/models/specvqgan/models/av_cond_transformer.py @@ -0,0 +1,528 @@ +import sys + +import pytorch_lightning as pl +import torch +import torch.nn as nn +import torch.nn.functional as F +from torchvision import transforms +import torchaudio +from omegaconf.listconfig import ListConfig + +sys.path.insert(0, '.') # nopep8 +from foleycrafter.models.specvqgan.modules.transformer.mingpt import (GPTClass, GPTFeats, GPTFeatsClass) +from foleycrafter.models.specvqgan.data.transforms import Wave2Spectrogram, PitchShift, NormalizeAudio +from train import instantiate_from_config + +SR = 22050 + +def disabled_train(self, mode=True): + """Overwrite model.train with this function to make sure train/eval mode + does not change anymore.""" + return self + + +class Net2NetTransformerAVCond(pl.LightningModule): + def __init__(self, transformer_config, first_stage_config, + cond_stage_config, + drop_condition=False, drop_video=False, drop_cond_video=False, + first_stage_permuter_config=None, cond_stage_permuter_config=None, + ckpt_path=None, ignore_keys=[], + first_stage_key="image", + cond_first_stage_key="cond_image", + cond_stage_key="depth", + downsample_cond_size=-1, + pkeep=1.0, + clip=30, + p_audio_aug=0.5, + p_pitch_shift=0., + p_normalize=0., + mel_num=80, + spec_crop_len=160): + + super().__init__() + self.init_first_stage_from_ckpt(first_stage_config) + self.init_cond_stage_from_ckpt(cond_stage_config) + if first_stage_permuter_config is None: + first_stage_permuter_config = {"target": "foleycrafter.models.specvqgan.modules.transformer.permuter.Identity"} + if cond_stage_permuter_config is None: + cond_stage_permuter_config = {"target": "foleycrafter.models.specvqgan.modules.transformer.permuter.Identity"} + self.first_stage_permuter = instantiate_from_config(config=first_stage_permuter_config) + self.cond_stage_permuter = instantiate_from_config(config=cond_stage_permuter_config) + self.transformer = instantiate_from_config(config=transformer_config) + + self.wav_transforms = nn.Sequential( + transforms.RandomApply([NormalizeAudio()], p=p_normalize), + transforms.RandomApply([PitchShift()], p=p_pitch_shift), + torchaudio.transforms.Spectrogram( + n_fft=1024, + hop_length=1024//4, + power=1, + ), + # transforms.RandomApply([ + # torchaudio.transforms.FrequencyMasking(freq_mask_param=40, iid_masks=False) + # ], p=p_audio_aug), + # transforms.RandomApply([ + # torchaudio.transforms.TimeMasking(time_mask_param=int(32 * 2), iid_masks=False) + # ], p=p_audio_aug), + torchaudio.transforms.MelScale( + n_mels=80, + sample_rate=SR, + f_min=125, + f_max=7600, + n_stft=513, + norm='slaney' + ), + Wave2Spectrogram(mel_num, spec_crop_len), + ) + ignore_keys = ['wav_transforms'] + + if ckpt_path is not None: + self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys) + self.drop_condition = drop_condition + self.drop_video = drop_video + self.drop_cond_video = drop_cond_video + print(f'>>> Feature setting: all cond: {self.drop_condition}, video: {self.drop_video}, cond video: {self.drop_cond_video}') + self.first_stage_key = first_stage_key + self.cond_first_stage_key = cond_first_stage_key + self.cond_stage_key = cond_stage_key + self.downsample_cond_size = downsample_cond_size + self.pkeep = pkeep + self.clip = clip + print('>>> model init done.') + + def init_from_ckpt(self, path, ignore_keys=list()): + sd = torch.load(path, map_location="cpu")["state_dict"] + for k in sd.keys(): + for ik in ignore_keys: + if k.startswith(ik): + self.print("Deleting key {} from state_dict.".format(k)) + del sd[k] + self.load_state_dict(sd, strict=False) + print(f"Restored from {path}") + + def init_first_stage_from_ckpt(self, config): + model = instantiate_from_config(config) + model = model.eval() + model.train = disabled_train + self.first_stage_model = model + + def init_cond_stage_from_ckpt(self, config): + model = instantiate_from_config(config) + model = model.eval() + model.train = disabled_train + self.cond_stage_model = model + + def forward(self, x, c, xp): + # one step to produce the logits + _, z_indices = self.encode_to_z(x) # VQ-GAN encoding + _, zp_indices = self.encode_to_z(xp) + _, c_indices = self.encode_to_c(c) # Conv1-1 down dim + col-major permuter + z_indices = z_indices[:, :self.clip] + zp_indices = zp_indices[:, :self.clip] + if not self.drop_condition: + z_indices = torch.cat([zp_indices, z_indices], dim=1) + + if self.training and self.pkeep < 1.0: + mask = torch.bernoulli(self.pkeep * torch.ones(z_indices.shape, device=z_indices.device)) + mask = mask.round().to(dtype=torch.int64) + r_indices = torch.randint_like(z_indices, self.transformer.config.vocab_size) + a_indices = mask*z_indices+(1-mask)*r_indices + else: + a_indices = z_indices + + # target includes all sequence elements (no need to handle first one + # differently because we are conditioning) + if self.drop_condition: + target = z_indices + else: + target = z_indices[:, self.clip:] + + # in the case we do not want to encode condition anyhow (e.g. inputs are features) + if isinstance(self.transformer, (GPTFeats, GPTClass, GPTFeatsClass)): + # make the prediction + logits, _, _ = self.transformer(z_indices[:, :-1], c) + # cut off conditioning outputs - output i corresponds to p(z_i | z_{ -1: + c = F.interpolate(c, size=(self.downsample_cond_size, self.downsample_cond_size)) + quant_c, _, info = self.cond_stage_model.encode(c) + if isinstance(self.transformer, (GPTFeats, GPTClass, GPTFeatsClass)): + # these are not indices but raw features or a class + indices = info[2] + else: + indices = info[2].view(quant_c.shape[0], -1) + indices = self.cond_stage_permuter(indices) + return quant_c, indices + + @torch.no_grad() + def decode_to_img(self, index, zshape, stage='first'): + if stage == 'first': + index = self.first_stage_permuter(index, reverse=True) + elif stage == 'cond': + print('in cond stage in decode_to_img which is unexpected ') + index = self.cond_stage_permuter(index, reverse=True) + else: + raise NotImplementedError + + bhwc = (zshape[0], zshape[2], zshape[3], zshape[1]) + quant_z = self.first_stage_model.quantize.get_codebook_entry(index.reshape(-1), shape=bhwc) + x = self.first_stage_model.decode(quant_z) + return x + + @torch.no_grad() + def log_images(self, batch, temperature=None, top_k=None, callback=None, lr_interface=False, **kwargs): + log = dict() + + N = 4 + if lr_interface: + x, c, xp = self.get_xcxp(batch, N, diffuse=False, upsample_factor=8) + else: + x, c, xp = self.get_xcxp(batch, N) + x = x.to(device=self.device) + xp = xp.to(device=self.device) + # c = c.to(device=self.device) + if isinstance(c, dict): + c = {k: v.to(self.device) for k, v in c.items()} + else: + c = c.to(self.device) + + quant_z, z_indices = self.encode_to_z(x) + quant_zp, zp_indices = self.encode_to_z(xp) + quant_c, c_indices = self.encode_to_c(c) # output can be features or a single class or a featcls dict + z_indices_rec = z_indices.clone() + zp_indices_clip = zp_indices[:, :self.clip] + z_indices_clip = z_indices[:, :self.clip] + + # create a "half"" sample + z_start_indices = z_indices_clip[:, :z_indices_clip.shape[1]//2] + if self.drop_condition: + steps = z_indices_clip.shape[1]-z_start_indices.shape[1] + else: + z_start_indices = torch.cat([zp_indices_clip, z_start_indices], dim=-1) + steps = 2*z_indices_clip.shape[1]-z_start_indices.shape[1] + index_sample, att_half = self.sample(z_start_indices, c_indices, + steps=steps, + temperature=temperature if temperature is not None else 1.0, + sample=True, + top_k=top_k if top_k is not None else 100, + callback=callback if callback is not None else lambda k: None) + if self.drop_condition: + z_indices_rec[:, :self.clip] = index_sample + else: + z_indices_rec[:, :self.clip] = index_sample[:, self.clip:] + x_sample = self.decode_to_img(z_indices_rec, quant_z.shape) + + # sample + z_start_indices = z_indices_clip[:, :0] + if not self.drop_condition: + z_start_indices = torch.cat([zp_indices_clip, z_start_indices], dim=-1) + index_sample, att_nopix = self.sample(z_start_indices, c_indices, + steps=z_indices_clip.shape[1], + temperature=temperature if temperature is not None else 1.0, + sample=True, + top_k=top_k if top_k is not None else 100, + callback=callback if callback is not None else lambda k: None) + if self.drop_condition: + z_indices_rec[:, :self.clip] = index_sample + else: + z_indices_rec[:, :self.clip] = index_sample[:, self.clip:] + x_sample_nopix = self.decode_to_img(z_indices_rec, quant_z.shape) + + # det sample + z_start_indices = z_indices_clip[:, :0] + if not self.drop_condition: + z_start_indices = torch.cat([zp_indices_clip, z_start_indices], dim=-1) + index_sample, att_det = self.sample(z_start_indices, c_indices, + steps=z_indices_clip.shape[1], + sample=False, + callback=callback if callback is not None else lambda k: None) + if self.drop_condition: + z_indices_rec[:, :self.clip] = index_sample + else: + z_indices_rec[:, :self.clip] = index_sample[:, self.clip:] + x_sample_det = self.decode_to_img(z_indices_rec, quant_z.shape) + + # reconstruction + x_rec = self.decode_to_img(z_indices, quant_z.shape) + + log["inputs"] = x + log["reconstructions"] = x_rec + + if isinstance(self.cond_stage_key, str): + cond_is_not_image = self.cond_stage_key != "image" + cond_has_segmentation = self.cond_stage_key == "segmentation" + elif isinstance(self.cond_stage_key, ListConfig): + cond_is_not_image = 'image' not in self.cond_stage_key + cond_has_segmentation = 'segmentation' in self.cond_stage_key + else: + raise NotImplementedError + + if cond_is_not_image: + cond_rec = self.cond_stage_model.decode(quant_c) + if cond_has_segmentation: + # get image from segmentation mask + num_classes = cond_rec.shape[1] + + c = torch.argmax(c, dim=1, keepdim=True) + c = F.one_hot(c, num_classes=num_classes) + c = c.squeeze(1).permute(0, 3, 1, 2).float() + c = self.cond_stage_model.to_rgb(c) + + cond_rec = torch.argmax(cond_rec, dim=1, keepdim=True) + cond_rec = F.one_hot(cond_rec, num_classes=num_classes) + cond_rec = cond_rec.squeeze(1).permute(0, 3, 1, 2).float() + cond_rec = self.cond_stage_model.to_rgb(cond_rec) + log["conditioning_rec"] = cond_rec + log["conditioning"] = c + + log["samples_half"] = x_sample + log["samples_nopix"] = x_sample_nopix + log["samples_det"] = x_sample_det + log["att_half"] = att_half + log["att_nopix"] = att_nopix + log["att_det"] = att_det + return log + + def spec_transform(self, batch): + wav = batch[self.first_stage_key] + wav_cond = batch[self.cond_first_stage_key] + N = wav.shape[0] + wav_cat = torch.cat([wav, wav_cond], dim=0) + self.wav_transforms.to(wav_cat.device) + spec = self.wav_transforms(wav_cat.to(torch.float32)) + batch[self.first_stage_key] = 2 * spec[:N] - 1 + batch[self.cond_first_stage_key] = 2 * spec[N:] - 1 + return batch + + def get_input(self, key, batch): + if isinstance(key, str): + # if batch[key] is 1D; else the batch[key] is 2D + if key in ['feature', 'target']: + if self.drop_condition or self.drop_cond_video: + cond_size = batch[key].shape[1] // 2 + batch[key] = batch[key][:, cond_size:] + x = self.cond_stage_model.get_input( + batch, key, drop_cond=(self.drop_condition or self.drop_cond_video) + ) + else: + x = batch[key] + if len(x.shape) == 3: + x = x[..., None] + x = x.permute(0, 3, 1, 2).to(memory_format=torch.contiguous_format) + if x.dtype == torch.double: + x = x.float() + elif isinstance(key, ListConfig): + x = self.cond_stage_model.get_input(batch, key) + for k, v in x.items(): + if v.dtype == torch.double: + x[k] = v.float() + return x + + def get_xcxp(self, batch, N=None): + if len(batch[self.first_stage_key].shape) == 2: + batch = self.spec_transform(batch) + x = self.get_input(self.first_stage_key, batch) + c = self.get_input(self.cond_stage_key, batch) + xp = self.get_input(self.cond_first_stage_key, batch) + if N is not None: + x = x[:N] + xp = xp[:N] + if isinstance(self.cond_stage_key, ListConfig): + c = {k: v[:N] for k, v in c.items()} + else: + c = c[:N] + # Drop additional information during training + if self.drop_condition: + xp[:] = 0 + if self.drop_video: + c[:] = 0 + return x, c, xp + + def shared_step(self, batch, batch_idx): + x, c, xp = self.get_xcxp(batch) + logits, target = self(x, c, xp) + loss = F.cross_entropy(logits.reshape(-1, logits.size(-1)), target.reshape(-1)) + return loss + + def training_step(self, batch, batch_idx): + loss = self.shared_step(batch, batch_idx) + self.log("train/loss", loss, prog_bar=True, logger=True, on_step=True, on_epoch=True) + return loss + + def validation_step(self, batch, batch_idx): + loss = self.shared_step(batch, batch_idx) + self.log("val/loss", loss, prog_bar=True, logger=True, on_step=True, on_epoch=True) + return loss + + def configure_optimizers(self): + """ + Following minGPT: + This long function is unfortunately doing something very simple and is being very defensive: + We are separating out all parameters of the model into two buckets: those that will experience + weight decay for regularization and those that won't (biases, and layernorm/embedding weights). + We are then returning the PyTorch optimizer object. + """ + # separate out all parameters to those that will and won't experience regularizing weight decay + decay = set() + no_decay = set() + whitelist_weight_modules = (torch.nn.Linear, ) + + blacklist_weight_modules = (torch.nn.LayerNorm, torch.nn.Embedding, torch.nn.Conv1d, torch.nn.LSTM, torch.nn.GRU) + for mn, m in self.transformer.named_modules(): + for pn, p in m.named_parameters(): + fpn = '%s.%s' % (mn, pn) if mn else pn # full param name + + if pn.endswith('bias'): + # all biases will not be decayed + no_decay.add(fpn) + elif pn.endswith('weight') and isinstance(m, whitelist_weight_modules): + # weights of whitelist modules will be weight decayed + decay.add(fpn) + elif pn.endswith('weight') and isinstance(m, blacklist_weight_modules): + # weights of blacklist modules will NOT be weight decayed + no_decay.add(fpn) + elif ('weight' in pn or 'bias' in pn) and isinstance(m, (torch.nn.LSTM, torch.nn.GRU)): + no_decay.add(fpn) + + # special case the position embedding parameter in the root GPT module as not decayed + no_decay.add('pos_emb') + + # validate that we considered every parameter + param_dict = {pn: p for pn, p in self.transformer.named_parameters()} + inter_params = decay & no_decay + union_params = decay | no_decay + assert len(inter_params) == 0, "parameters %s made it into both decay/no_decay sets!" % (str(inter_params), ) + assert len(param_dict.keys() - union_params) == 0, "parameters %s were not separated into either decay/no_decay set!" \ + % (str(param_dict.keys() - union_params), ) + + # create the pytorch optimizer object + optim_groups = [ + {"params": [param_dict[pn] for pn in sorted(list(decay))], "weight_decay": 0.01}, + {"params": [param_dict[pn] for pn in sorted(list(no_decay))], "weight_decay": 0.0}, + ] + optimizer = torch.optim.AdamW(optim_groups, lr=self.learning_rate, betas=(0.9, 0.95)) + return optimizer + + +if __name__ == '__main__': + from omegaconf import OmegaConf + + cfg_image = OmegaConf.load('./configs/vggsound_transformer.yaml') + cfg_image.model.params.first_stage_config.params.ckpt_path = './logs/2021-05-19T22-16-54_vggsound_codebook/checkpoints/last.ckpt' + + transformer_cfg = cfg_image.model.params.transformer_config + first_stage_cfg = cfg_image.model.params.first_stage_config + cond_stage_cfg = cfg_image.model.params.cond_stage_config + permuter_cfg = cfg_image.model.params.permuter_config + transformer = Net2NetTransformerAVCond( + transformer_cfg, first_stage_cfg, cond_stage_cfg, permuter_cfg + ) + + c = torch.rand(2, 2048, 212) + x = torch.rand(2, 1, 80, 848) + + logits, target = transformer(x, c) + print(logits.shape, target.shape) diff --git a/foleycrafter/models/specvqgan/models/cond_transformer.py b/foleycrafter/models/specvqgan/models/cond_transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..62e5168e511df7940f0a0933bb4cd7d6cf6da873 --- /dev/null +++ b/foleycrafter/models/specvqgan/models/cond_transformer.py @@ -0,0 +1,455 @@ +import sys + +import pytorch_lightning as pl +import torch +import torch.nn as nn +import torch.nn.functional as F +from omegaconf.listconfig import ListConfig +from torchvision import transforms +from foleycrafter.models.specvqgan.data.transforms import Wave2Spectrogram +import torchaudio + +sys.path.insert(0, '.') # nopep8 +from foleycrafter.models.specvqgan.modules.transformer.mingpt import (GPTClass, GPTFeats, GPTFeatsClass) +from train import instantiate_from_config + + +def disabled_train(self, mode=True): + """Overwrite model.train with this function to make sure train/eval mode + does not change anymore.""" + return self + + +class Net2NetTransformer(pl.LightningModule): + def __init__(self, transformer_config, first_stage_config, + cond_stage_config, + first_stage_permuter_config=None, cond_stage_permuter_config=None, + ckpt_path=None, ignore_keys=[], + first_stage_key="image", + cond_stage_key="depth", + downsample_cond_size=-1, + pkeep=1.0, + mel_num=80, + spec_crop_len=160): + + super().__init__() + self.init_first_stage_from_ckpt(first_stage_config) + self.init_cond_stage_from_ckpt(cond_stage_config) + if first_stage_permuter_config is None: + first_stage_permuter_config = {"target": "foleycrafter.models.specvqgan.modules.transformer.permuter.Identity"} + if cond_stage_permuter_config is None: + cond_stage_permuter_config = {"target": "foleycrafter.models.specvqgan.modules.transformer.permuter.Identity"} + self.first_stage_permuter = instantiate_from_config(config=first_stage_permuter_config) + self.cond_stage_permuter = instantiate_from_config(config=cond_stage_permuter_config) + self.transformer = instantiate_from_config(config=transformer_config) + + self.wav_transforms = nn.Sequential( + torchaudio.transforms.Spectrogram( + n_fft=1024, + hop_length=1024//4, + power=1, + ), + torchaudio.transforms.MelScale( + n_mels=80, + sample_rate=22050, + f_min=125, + f_max=7600, + n_stft=513, + norm='slaney' + ), + Wave2Spectrogram(mel_num, spec_crop_len), + ) + ignore_keys = ['wav_transforms'] + + if ckpt_path is not None: + self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys) + self.first_stage_key = first_stage_key + self.cond_stage_key = cond_stage_key + self.downsample_cond_size = downsample_cond_size + self.pkeep = pkeep + print('>>> model init done.') + + def init_from_ckpt(self, path, ignore_keys=list()): + sd = torch.load(path, map_location="cpu")["state_dict"] + for k in sd.keys(): + for ik in ignore_keys: + if k.startswith(ik): + self.print("Deleting key {} from state_dict.".format(k)) + del sd[k] + self.load_state_dict(sd, strict=False) + print(f"Restored from {path}") + + def init_first_stage_from_ckpt(self, config): + model = instantiate_from_config(config) + model = model.eval() + model.train = disabled_train + self.first_stage_model = model + + def init_cond_stage_from_ckpt(self, config): + model = instantiate_from_config(config) + model = model.eval() + model.train = disabled_train + self.cond_stage_model = model + + def forward(self, x, c): + # one step to produce the logits + _, z_indices = self.encode_to_z(x) + _, c_indices = self.encode_to_c(c) + + if self.training and self.pkeep < 1.0: + mask = torch.bernoulli(self.pkeep * torch.ones(z_indices.shape, device=z_indices.device)) + mask = mask.round().to(dtype=torch.int64) + r_indices = torch.randint_like(z_indices, self.transformer.config.vocab_size) + a_indices = mask*z_indices+(1-mask)*r_indices + else: + a_indices = z_indices + + # target includes all sequence elements (no need to handle first one + # differently because we are conditioning) + target = z_indices + + # in the case we do not want to encode condition anyhow (e.g. inputs are features) + if isinstance(self.transformer, (GPTFeats, GPTClass, GPTFeatsClass)): + # make the prediction + logits, _, _ = self.transformer(z_indices[:, :-1], c) + # cut off conditioning outputs - output i corresponds to p(z_i | z_{ -1: + c = F.interpolate(c, size=(self.downsample_cond_size, self.downsample_cond_size)) + quant_c, _, info = self.cond_stage_model.encode(c) + if isinstance(self.transformer, (GPTFeats, GPTClass, GPTFeatsClass)): + # these are not indices but raw features or a class + indices = info[2] + else: + indices = info[2].view(quant_c.shape[0], -1) + indices = self.cond_stage_permuter(indices) + return quant_c, indices + + @torch.no_grad() + def decode_to_img(self, index, zshape, stage='first'): + if stage == 'first': + index = self.first_stage_permuter(index, reverse=True) + elif stage == 'cond': + print('in cond stage in decode_to_img which is unexpected ') + index = self.cond_stage_permuter(index, reverse=True) + else: + raise NotImplementedError + + bhwc = (zshape[0], zshape[2], zshape[3], zshape[1]) + quant_z = self.first_stage_model.quantize.get_codebook_entry(index.reshape(-1), shape=bhwc) + x = self.first_stage_model.decode(quant_z) + return x + + @torch.no_grad() + def log_images(self, batch, temperature=None, top_k=None, callback=None, lr_interface=False, **kwargs): + log = dict() + + N = 4 + if lr_interface: + x, c = self.get_xc(batch, N, diffuse=False, upsample_factor=8) + else: + x, c = self.get_xc(batch, N) + x = x.to(device=self.device) + # c = c.to(device=self.device) + if isinstance(c, dict): + c = {k: v.to(self.device) for k, v in c.items()} + else: + c = c.to(self.device) + + quant_z, z_indices = self.encode_to_z(x) + quant_c, c_indices = self.encode_to_c(c) # output can be features or a single class or a featcls dict + + # create a "half"" sample + z_start_indices = z_indices[:, :z_indices.shape[1]//2] + index_sample, att_half = self.sample(z_start_indices, c_indices, + steps=z_indices.shape[1]-z_start_indices.shape[1], + temperature=temperature if temperature is not None else 1.0, + sample=True, + top_k=top_k if top_k is not None else 100, + callback=callback if callback is not None else lambda k: None) + x_sample = self.decode_to_img(index_sample, quant_z.shape) + + # sample + z_start_indices = z_indices[:, :0] + index_sample, att_nopix = self.sample(z_start_indices, c_indices, + steps=z_indices.shape[1], + temperature=temperature if temperature is not None else 1.0, + sample=True, + top_k=top_k if top_k is not None else 100, + callback=callback if callback is not None else lambda k: None) + x_sample_nopix = self.decode_to_img(index_sample, quant_z.shape) + + # det sample + z_start_indices = z_indices[:, :0] + index_sample, att_det = self.sample(z_start_indices, c_indices, + steps=z_indices.shape[1], + sample=False, + callback=callback if callback is not None else lambda k: None) + x_sample_det = self.decode_to_img(index_sample, quant_z.shape) + + # reconstruction + x_rec = self.decode_to_img(z_indices, quant_z.shape) + + log["inputs"] = x + log["reconstructions"] = x_rec + + if isinstance(self.cond_stage_key, str): + cond_is_not_image = self.cond_stage_key != "image" + cond_has_segmentation = self.cond_stage_key == "segmentation" + elif isinstance(self.cond_stage_key, ListConfig): + cond_is_not_image = 'image' not in self.cond_stage_key + cond_has_segmentation = 'segmentation' in self.cond_stage_key + else: + raise NotImplementedError + + if cond_is_not_image: + cond_rec = self.cond_stage_model.decode(quant_c) + if cond_has_segmentation: + # get image from segmentation mask + num_classes = cond_rec.shape[1] + + c = torch.argmax(c, dim=1, keepdim=True) + c = F.one_hot(c, num_classes=num_classes) + c = c.squeeze(1).permute(0, 3, 1, 2).float() + c = self.cond_stage_model.to_rgb(c) + + cond_rec = torch.argmax(cond_rec, dim=1, keepdim=True) + cond_rec = F.one_hot(cond_rec, num_classes=num_classes) + cond_rec = cond_rec.squeeze(1).permute(0, 3, 1, 2).float() + cond_rec = self.cond_stage_model.to_rgb(cond_rec) + log["conditioning_rec"] = cond_rec + log["conditioning"] = c + + log["samples_half"] = x_sample + log["samples_nopix"] = x_sample_nopix + log["samples_det"] = x_sample_det + log["att_half"] = att_half + log["att_nopix"] = att_nopix + log["att_det"] = att_det + return log + + def spec_transform(self, batch): + wav = batch[self.first_stage_key] + N = wav.shape[0] + self.wav_transforms.to(wav.device) + spec = self.wav_transforms(wav.to(torch.float32)) + batch[self.first_stage_key] = 2 * spec[:N] - 1 + return batch + + def get_input(self, key, batch): + if isinstance(key, str): + # if batch[key] is 1D; else the batch[key] is 2D + if key in ['feature', 'target']: + x = self.cond_stage_model.get_input(batch, key) + else: + x = batch[key] + if len(x.shape) == 3: + x = x[..., None] + x = x.permute(0, 3, 1, 2).to(memory_format=torch.contiguous_format) + if x.dtype == torch.double: + x = x.float() + elif isinstance(key, ListConfig): + x = self.cond_stage_model.get_input(batch, key) + for k, v in x.items(): + if v.dtype == torch.double: + x[k] = v.float() + return x + + def get_xc(self, batch, N=None): + if len(batch[self.first_stage_key].shape) == 2: + batch = self.spec_transform(batch) + x = self.get_input(self.first_stage_key, batch) + c = self.get_input(self.cond_stage_key, batch) + if N is not None: + x = x[:N] + if isinstance(self.cond_stage_key, ListConfig): + c = {k: v[:N] for k, v in c.items()} + else: + c = c[:N] + return x, c + + def shared_step(self, batch, batch_idx): + x, c = self.get_xc(batch) + logits, target = self(x, c) + loss = F.cross_entropy(logits.reshape(-1, logits.size(-1)), target.reshape(-1)) + return loss + + def training_step(self, batch, batch_idx): + loss = self.shared_step(batch, batch_idx) + self.log("train/loss", loss, prog_bar=True, logger=True, on_step=True, on_epoch=True) + return loss + + def validation_step(self, batch, batch_idx): + loss = self.shared_step(batch, batch_idx) + self.log("val/loss", loss, prog_bar=True, logger=True, on_step=True, on_epoch=True) + return loss + + def configure_optimizers(self): + """ + Following minGPT: + This long function is unfortunately doing something very simple and is being very defensive: + We are separating out all parameters of the model into two buckets: those that will experience + weight decay for regularization and those that won't (biases, and layernorm/embedding weights). + We are then returning the PyTorch optimizer object. + """ + # separate out all parameters to those that will and won't experience regularizing weight decay + decay = set() + no_decay = set() + whitelist_weight_modules = (torch.nn.Linear, ) + + blacklist_weight_modules = (torch.nn.LayerNorm, torch.nn.Embedding, torch.nn.Conv1d, torch.nn.LSTM, torch.nn.GRU) + for mn, m in self.transformer.named_modules(): + for pn, p in m.named_parameters(): + fpn = '%s.%s' % (mn, pn) if mn else pn # full param name + + if pn.endswith('bias'): + # all biases will not be decayed + no_decay.add(fpn) + elif pn.endswith('weight') and isinstance(m, whitelist_weight_modules): + # weights of whitelist modules will be weight decayed + decay.add(fpn) + elif pn.endswith('weight') and isinstance(m, blacklist_weight_modules): + # weights of blacklist modules will NOT be weight decayed + no_decay.add(fpn) + elif ('weight' in pn or 'bias' in pn) and isinstance(m, (torch.nn.LSTM, torch.nn.GRU)): + no_decay.add(fpn) + + # special case the position embedding parameter in the root GPT module as not decayed + no_decay.add('pos_emb') + + # validate that we considered every parameter + param_dict = {pn: p for pn, p in self.transformer.named_parameters()} + inter_params = decay & no_decay + union_params = decay | no_decay + assert len(inter_params) == 0, "parameters %s made it into both decay/no_decay sets!" % (str(inter_params), ) + assert len(param_dict.keys() - union_params) == 0, "parameters %s were not separated into either decay/no_decay set!" \ + % (str(param_dict.keys() - union_params), ) + + # create the pytorch optimizer object + optim_groups = [ + {"params": [param_dict[pn] for pn in sorted(list(decay))], "weight_decay": 0.01}, + {"params": [param_dict[pn] for pn in sorted(list(no_decay))], "weight_decay": 0.0}, + ] + optimizer = torch.optim.AdamW(optim_groups, lr=self.learning_rate, betas=(0.9, 0.95)) + return optimizer + + +if __name__ == '__main__': + from omegaconf import OmegaConf + + cfg_image = OmegaConf.load('./configs/vggsound_transformer.yaml') + cfg_image.model.params.first_stage_config.params.ckpt_path = './logs/2021-05-19T22-16-54_vggsound_codebook/checkpoints/last.ckpt' + + transformer_cfg = cfg_image.model.params.transformer_config + first_stage_cfg = cfg_image.model.params.first_stage_config + cond_stage_cfg = cfg_image.model.params.cond_stage_config + permuter_cfg = cfg_image.model.params.permuter_config + transformer = Net2NetTransformer( + transformer_cfg, first_stage_cfg, cond_stage_cfg, permuter_cfg + ) + + c = torch.rand(2, 2048, 212) + x = torch.rand(2, 1, 80, 160) + + logits, target = transformer(x, c) + print(logits.shape, target.shape) diff --git a/foleycrafter/models/specvqgan/models/vqgan.py b/foleycrafter/models/specvqgan/models/vqgan.py new file mode 100644 index 0000000000000000000000000000000000000000..58e7273b3153dc0f370a763de11165169cc2db91 --- /dev/null +++ b/foleycrafter/models/specvqgan/models/vqgan.py @@ -0,0 +1,397 @@ +import torch +import torch.nn as nn +import torchaudio +from torchvision import transforms +import torch.nn.functional as F +import pytorch_lightning as pl + +import sys +import math +sys.path.insert(0, '.') # nopep8 +from train import instantiate_from_config +from foleycrafter.models.specvqgan.data.transforms import Wave2Spectrogram, NormalizeAudio + +from foleycrafter.models.specvqgan.modules.diffusionmodules.model import Encoder, Decoder, Encoder1d, Decoder1d +from foleycrafter.models.specvqgan.modules.vqvae.quantize import VectorQuantizer, VectorQuantizer1d + + +class VQModel(pl.LightningModule): + def __init__(self, + ddconfig, + lossconfig, + n_embed, + embed_dim, + ckpt_path=None, + ignore_keys=[], + image_key="image", + colorize_nlabels=None, + monitor=None, + L=10., + mel_num=80, + spec_crop_len=160, + normalize=False, + freeze_encoder=False, + ): + super().__init__() + self.image_key = image_key + # we need this one for compatibility in train.ImageLogger.log_img if statement + self.first_stage_key = image_key + self.encoder = Encoder(**ddconfig) + self.decoder = Decoder(**ddconfig) + self.loss = instantiate_from_config(lossconfig) + self.quantize = VectorQuantizer(n_embed, embed_dim, beta=0.25) + self.quant_conv = torch.nn.Conv2d(ddconfig["z_channels"], embed_dim, 1) + self.post_quant_conv = torch.nn.Conv2d(embed_dim, ddconfig["z_channels"], 1) + + aug_list = [ + torchaudio.transforms.Spectrogram( + n_fft=1024, + hop_length=1024//4, + power=1, + ), + torchaudio.transforms.MelScale( + n_mels=80, + sample_rate=22050, + f_min=125, + f_max=7600, + n_stft=513, + norm='slaney' + ), + Wave2Spectrogram(mel_num, spec_crop_len), + ] + if normalize: + aug_list = [transforms.RandomApply([NormalizeAudio()], p=1. if normalize else 0.)] + aug_list + + if not freeze_encoder: + self.wav_transforms = nn.Sequential(*aug_list) + ignore_keys += ['first_stage_model.wav_transforms', 'wav_transforms'] + + if ckpt_path is not None: + self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys) + if colorize_nlabels is not None: + assert type(colorize_nlabels)==int + self.register_buffer("colorize", torch.randn(3, colorize_nlabels, 1, 1)) + if monitor is not None: + self.monitor = monitor + self.used_codes = [] + self.counts = [0 for _ in range(self.quantize.n_e)] + + if freeze_encoder: + for param in self.encoder.parameters(): + param.requires_grad = False + for param in self.quantize.parameters(): + param.requires_grad = False + for param in self.quant_conv.parameters(): + param.requires_grad = False + + def init_from_ckpt(self, path, ignore_keys=list()): + sd = torch.load(path, map_location="cpu")["state_dict"] + keys = list(sd.keys()) + for k in keys: + for ik in ignore_keys: + if k.startswith(ik): + print("Deleting key {} from state_dict.".format(k)) + del sd[k] + self.load_state_dict(sd, strict=False) + print(f"Restored from {path}") + + def encode(self, x): + h = self.encoder(x) # 2d: (B, 256, 16, 16) <- (B, 3, 256, 256) + h = self.quant_conv(h) # 2d: (B, 256, 16, 16) + quant, emb_loss, info = self.quantize(h) # (B, 256, 16, 16), (), ((), (768, 1024), (768, 1)) + if not self.training: + self.counts = [info[2].squeeze().tolist().count(i) + self.counts[i] for i in range(self.quantize.n_e)] + return quant, emb_loss, info + + def decode(self, quant): + quant = self.post_quant_conv(quant) + dec = self.decoder(quant) + return dec + + def decode_code(self, code_b): + quant_b = self.quantize.embed_code(code_b) + dec = self.decode(quant_b) + return dec + + def forward(self, input): + quant, diff, _ = self.encode(input) + dec = self.decode(quant) + return dec, diff + + def get_input(self, batch, k): + x = batch[k] + if len(x.shape) == 2: + x = self.spec_trans(x) + if len(x.shape) == 3: + x = x[..., None] + x = x.permute(0, 3, 1, 2).to(memory_format=torch.contiguous_format) + return x.float() + + def spec_trans(self, wav): + self.wav_transforms.to(wav.device) + spec = self.wav_transforms(wav.to(torch.float32)) + return 2 * spec - 1 + + def training_step(self, batch, batch_idx, optimizer_idx): + x = self.get_input(batch, self.image_key) + xrec, qloss = self(x) + + if optimizer_idx == 0: + # autoencode + aeloss, log_dict_ae = self.loss(qloss, x, xrec, optimizer_idx, self.global_step, + last_layer=self.get_last_layer(), split="train") + + self.log("train/aeloss", aeloss, prog_bar=True, logger=True, on_step=True, on_epoch=True) + self.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=True) + return aeloss + + if optimizer_idx == 1: + # discriminator + discloss, log_dict_disc = self.loss(qloss, x, xrec, optimizer_idx, self.global_step, + last_layer=self.get_last_layer(), split="train") + self.log("train/disc_loss", discloss, prog_bar=True, logger=True, on_step=True, on_epoch=True) + self.log_dict(log_dict_disc, prog_bar=False, logger=True, on_step=True, on_epoch=True) + return discloss + + def validation_step(self, batch, batch_idx): + if batch_idx == 0 and self.global_step != 0 and sum(self.counts) > 0: + zero_hit_codes = len([1 for count in self.counts if count == 0]) + used_codes = [] + for c, count in enumerate(self.counts): + used_codes.extend([c] * count) + self.logger.experiment.add_histogram('val/code_hits', torch.tensor(used_codes), self.global_step) + self.logger.experiment.add_scalar('val/zero_hit_codes', zero_hit_codes, self.global_step) + self.counts = [0 for _ in range(self.quantize.n_e)] + x = self.get_input(batch, self.image_key) + xrec, qloss = self(x) + aeloss, log_dict_ae = self.loss(qloss, x, xrec, 0, self.global_step, + last_layer=self.get_last_layer(), split="val") + + discloss, log_dict_disc = self.loss(qloss, x, xrec, 1, self.global_step, + last_layer=self.get_last_layer(), split="val") + rec_loss = log_dict_ae['val/rec_loss'] + self.log('val/rec_loss', rec_loss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True) + self.log('val/aeloss', aeloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True) + self.log_dict(log_dict_ae) + self.log_dict(log_dict_disc) + return self.log_dict + + def configure_optimizers(self): + lr = self.learning_rate + opt_ae = torch.optim.Adam(list(self.encoder.parameters()) + + list(self.decoder.parameters()) + + list(self.quantize.parameters()) + + list(self.quant_conv.parameters()) + + list(self.post_quant_conv.parameters()), + lr=lr, betas=(0.5, 0.9)) + opt_disc = torch.optim.Adam(self.loss.discriminator.parameters(), + lr=lr, betas=(0.5, 0.9)) + return [opt_ae, opt_disc], [] + + def get_last_layer(self): + return self.decoder.conv_out.weight + + def log_images(self, batch, **kwargs): + log = dict() + x = self.get_input(batch, self.image_key) + x = x.to(self.device) + xrec, _ = self(x) + if x.shape[1] > 3: + # colorize with random projection + assert xrec.shape[1] > 3 + x = self.to_rgb(x) + xrec = self.to_rgb(xrec) + log["inputs"] = x + log["reconstructions"] = xrec + return log + + def to_rgb(self, x): + assert self.image_key == "segmentation" + if not hasattr(self, "colorize"): + self.register_buffer("colorize", torch.randn(3, x.shape[1], 1, 1).to(x)) + x = F.conv2d(x, weight=self.colorize) + x = 2.*(x-x.min())/(x.max()-x.min()) - 1. + return x + + +class VQModel1d(VQModel): + def __init__(self, ddconfig, lossconfig, n_embed, embed_dim, ckpt_path=None, ignore_keys=[], + image_key='feature', colorize_nlabels=None, monitor=None): + # ckpt_path is none to super because otherwise will try to load 1D checkpoint into 2D model + super().__init__(ddconfig, lossconfig, n_embed, embed_dim) + self.image_key = image_key + # we need this one for compatibility in train.ImageLogger.log_img if statement + self.first_stage_key = image_key + self.encoder = Encoder1d(**ddconfig) + self.decoder = Decoder1d(**ddconfig) + self.loss = instantiate_from_config(lossconfig) + self.quantize = VectorQuantizer1d(n_embed, embed_dim, beta=0.25) + self.quant_conv = torch.nn.Conv1d(ddconfig['z_channels'], embed_dim, 1) + self.post_quant_conv = torch.nn.Conv1d(embed_dim, ddconfig['z_channels'], 1) + if ckpt_path is not None: + self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys) + if colorize_nlabels is not None: + assert type(colorize_nlabels)==int + self.register_buffer('colorize', torch.randn(3, colorize_nlabels, 1, 1)) + if monitor is not None: + self.monitor = monitor + + def get_input(self, batch, k): + x = batch[k] + if self.image_key == 'feature': + x = x.permute(0, 2, 1) + elif self.image_key == 'image': + x = x.unsqueeze(1) + x = x.to(memory_format=torch.contiguous_format) + return x.float() + + def forward(self, input): + if self.image_key == 'image': + input = input.squeeze(1) + quant, diff, _ = self.encode(input) + dec = self.decode(quant) + if self.image_key == 'image': + dec = dec.unsqueeze(1) + return dec, diff + + def log_images(self, batch, **kwargs): + if self.image_key == 'image': + log = dict() + x = self.get_input(batch, self.image_key) + x = x.to(self.device) + xrec, _ = self(x) + if x.shape[1] > 3: + # colorize with random projection + assert xrec.shape[1] > 3 + x = self.to_rgb(x) + xrec = self.to_rgb(xrec) + log['inputs'] = x + log['reconstructions'] = xrec + return log + else: + raise NotImplementedError('1d input should be treated differently') + + def to_rgb(self, batch, **kwargs): + raise NotImplementedError('1d input should be treated differently') + + +class VQSegmentationModel(VQModel): + def __init__(self, n_labels, *args, **kwargs): + super().__init__(*args, **kwargs) + self.register_buffer("colorize", torch.randn(3, n_labels, 1, 1)) + + def configure_optimizers(self): + lr = self.learning_rate + opt_ae = torch.optim.Adam(list(self.encoder.parameters())+ + list(self.decoder.parameters())+ + list(self.quantize.parameters())+ + list(self.quant_conv.parameters())+ + list(self.post_quant_conv.parameters()), + lr=lr, betas=(0.5, 0.9)) + return opt_ae + + def training_step(self, batch, batch_idx): + x = self.get_input(batch, self.image_key) + xrec, qloss = self(x) + aeloss, log_dict_ae = self.loss(qloss, x, xrec, split="train") + self.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=True) + return aeloss + + def validation_step(self, batch, batch_idx): + x = self.get_input(batch, self.image_key) + xrec, qloss = self(x) + aeloss, log_dict_ae = self.loss(qloss, x, xrec, split="val") + self.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=True) + total_loss = log_dict_ae["val/total_loss"] + self.log("val/total_loss", total_loss, + prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True) + return aeloss + + @torch.no_grad() + def log_images(self, batch, **kwargs): + log = dict() + x = self.get_input(batch, self.image_key) + x = x.to(self.device) + xrec, _ = self(x) + if x.shape[1] > 3: + # colorize with random projection + assert xrec.shape[1] > 3 + # convert logits to indices + xrec = torch.argmax(xrec, dim=1, keepdim=True) + xrec = F.one_hot(xrec, num_classes=x.shape[1]) + xrec = xrec.squeeze(1).permute(0, 3, 1, 2).float() + x = self.to_rgb(x) + xrec = self.to_rgb(xrec) + log["inputs"] = x + log["reconstructions"] = xrec + return log + + +class VQNoDiscModel(VQModel): + def __init__(self, + ddconfig, + lossconfig, + n_embed, + embed_dim, + ckpt_path=None, + ignore_keys=[], + image_key="image", + colorize_nlabels=None + ): + super().__init__(ddconfig=ddconfig, lossconfig=lossconfig, n_embed=n_embed, embed_dim=embed_dim, + ckpt_path=ckpt_path, ignore_keys=ignore_keys, image_key=image_key, + colorize_nlabels=colorize_nlabels) + + def training_step(self, batch, batch_idx): + x = self.get_input(batch, self.image_key) + xrec, qloss = self(x) + # autoencode + aeloss, log_dict_ae = self.loss(qloss, x, xrec, self.global_step, split="train") + output = pl.TrainResult(minimize=aeloss) + output.log("train/aeloss", aeloss, + prog_bar=True, logger=True, on_step=True, on_epoch=True) + output.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=True) + return output + + def validation_step(self, batch, batch_idx): + x = self.get_input(batch, self.image_key) + xrec, qloss = self(x) + aeloss, log_dict_ae = self.loss(qloss, x, xrec, self.global_step, split="val") + rec_loss = log_dict_ae["val/rec_loss"] + output = pl.EvalResult(checkpoint_on=rec_loss) + output.log("val/rec_loss", rec_loss, + prog_bar=True, logger=True, on_step=True, on_epoch=True) + output.log("val/aeloss", aeloss, + prog_bar=True, logger=True, on_step=True, on_epoch=True) + output.log_dict(log_dict_ae) + + return output + + def configure_optimizers(self): + optimizer = torch.optim.Adam(list(self.encoder.parameters()) + + list(self.decoder.parameters()) + + list(self.quantize.parameters()) + + list(self.quant_conv.parameters()) + + list(self.post_quant_conv.parameters()), + lr=self.learning_rate, betas=(0.5, 0.9)) + return optimizer + + +if __name__ == '__main__': + from omegaconf import OmegaConf + from train import instantiate_from_config + + image_key = 'image' + cfg_audio = OmegaConf.load('./configs/vggsound_codebook.yaml') + model = VQModel(cfg_audio.model.params.ddconfig, + cfg_audio.model.params.lossconfig, + cfg_audio.model.params.n_embed, + cfg_audio.model.params.embed_dim, + image_key='image') + batch = { + 'image': torch.rand((4, 80, 848)), + 'file_path_': ['data/vggsound/mel123.npy', 'data/vggsound/mel123.npy', 'data/vggsound/mel123.npy'], + 'class': [1, 1, 1], + } + xrec, qloss = model(model.get_input(batch, image_key)) + print(xrec.shape, qloss.shape) diff --git a/foleycrafter/models/specvqgan/modules/diffusionmodules/model.py b/foleycrafter/models/specvqgan/modules/diffusionmodules/model.py new file mode 100644 index 0000000000000000000000000000000000000000..9a1ceb026e9be0cd864287800daff4df37f432c1 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/diffusionmodules/model.py @@ -0,0 +1,999 @@ +# pytorch_diffusion + derived encoder decoder +import math +import torch +import torch.nn as nn +import numpy as np + + +def get_timestep_embedding(timesteps, embedding_dim): + """ + This matches the implementation in Denoising Diffusion Probabilistic Models: + From Fairseq. + Build sinusoidal embeddings. + This matches the implementation in tensor2tensor, but differs slightly + from the description in Section 3.5 of "Attention Is All You Need". + """ + assert len(timesteps.shape) == 1 + + half_dim = embedding_dim // 2 + emb = math.log(10000) / (half_dim - 1) + emb = torch.exp(torch.arange(half_dim, dtype=torch.float32) * -emb) + emb = emb.to(device=timesteps.device) + emb = timesteps.float()[:, None] * emb[None, :] + emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1) + if embedding_dim % 2 == 1: # zero pad + emb = torch.nn.functional.pad(emb, (0,1,0,0)) + return emb + + +def nonlinearity(x): + # swish + return x*torch.sigmoid(x) + + +def Normalize(in_channels): + return torch.nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True) + +class Upsample(nn.Module): + def __init__(self, in_channels, with_conv): + super().__init__() + self.with_conv = with_conv + if self.with_conv: + self.conv = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + x = torch.nn.functional.interpolate(x, scale_factor=2.0, mode="nearest") + if self.with_conv: + x = self.conv(x) + return x + +class Upsample1d(Upsample): + def __init__(self, in_channels, with_conv): + super().__init__(in_channels, with_conv) + if self.with_conv: + self.conv = torch.nn.Conv1d(in_channels, in_channels, kernel_size=3, stride=1, padding=1) + +class Downsample(nn.Module): + def __init__(self, in_channels, with_conv): + super().__init__() + self.with_conv = with_conv + if self.with_conv: + # no asymmetric padding in torch conv, must do it ourselves + self.conv = torch.nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=2, padding=0) + self.pad = (0, 1, 0, 1) + else: + self.avg_pool = nn.AvgPool2d(kernel_size=2, stride=2) + + def forward(self, x): + if self.with_conv: # bp: check self.avgpool and self.pad + x = torch.nn.functional.pad(x, self.pad, mode="constant", value=0) + x = self.conv(x) + else: + x = self.avg_pool(x) + return x + +class Downsample1d(Downsample): + + def __init__(self, in_channels, with_conv): + super().__init__(in_channels, with_conv) + if self.with_conv: + # no asymmetric padding in torch conv, must do it ourselves + # TODO: can we replace it just with conv2d with padding 1? + self.conv = torch.nn.Conv1d(in_channels, in_channels, kernel_size=3, stride=2, padding=0) + self.pad = (1, 1) + else: + self.avg_pool = nn.AvgPool1d(kernel_size=2, stride=2) + + +class ResnetBlock(nn.Module): + def __init__(self, *, in_channels, out_channels=None, conv_shortcut=False, + dropout, temb_channels=512): + super().__init__() + self.in_channels = in_channels + out_channels = in_channels if out_channels is None else out_channels + self.out_channels = out_channels + self.use_conv_shortcut = conv_shortcut + + self.norm1 = Normalize(in_channels) + self.conv1 = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + if temb_channels > 0: + self.temb_proj = torch.nn.Linear(temb_channels, + out_channels) + self.norm2 = Normalize(out_channels) + self.dropout = torch.nn.Dropout(dropout) + self.conv2 = torch.nn.Conv2d(out_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + if self.in_channels != self.out_channels: + if self.use_conv_shortcut: + self.conv_shortcut = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + else: + self.nin_shortcut = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=1, + stride=1, + padding=0) + + def forward(self, x, temb): + h = x + h = self.norm1(h) + h = nonlinearity(h) + h = self.conv1(h) + + if temb is not None: + h = h + self.temb_proj(nonlinearity(temb))[:,:,None,None] + + h = self.norm2(h) + h = nonlinearity(h) + h = self.dropout(h) + h = self.conv2(h) + + if self.in_channels != self.out_channels: + if self.use_conv_shortcut: + x = self.conv_shortcut(x) + else: + x = self.nin_shortcut(x) + + return x+h + +class ResnetBlock1d(ResnetBlock): + def __init__(self, *, in_channels, out_channels=None, conv_shortcut=False, + dropout, temb_channels=512): + super().__init__(in_channels=in_channels, out_channels=out_channels, + conv_shortcut=conv_shortcut, dropout=dropout, temb_channels=temb_channels) + # redefining different elements (forward is goint to be the same as in RenetBlock) + if temb_channels > 0: + raise NotImplementedError('go to ResnetBlock and figure out how to deal with it in forward') + self.temb_proj = torch.nn.Linear(temb_channels, out_channels) + + self.conv1 = torch.nn.Conv1d(in_channels, out_channels, kernel_size=3, stride=1, padding=1) + self.conv2 = torch.nn.Conv1d(out_channels, out_channels, kernel_size=3, stride=1, padding=1) + if self.in_channels != self.out_channels: + if self.use_conv_shortcut: + self.conv_shortcut = torch.nn.Conv1d(in_channels, out_channels, kernel_size=3, + stride=1, padding=1) + else: + self.nin_shortcut = torch.nn.Conv1d(in_channels, out_channels, kernel_size=1, + stride=1, padding=0) + + +class AttnBlock(nn.Module): + def __init__(self, in_channels): + super().__init__() + self.in_channels = in_channels + + self.norm = Normalize(in_channels) + self.q = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.k = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.v = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.proj_out = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + + + def forward(self, x): + h_ = x + h_ = self.norm(h_) + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + + # compute attention + b,c,h,w = q.shape + q = q.reshape(b,c,h*w) + q = q.permute(0,2,1) # b,hw,c + k = k.reshape(b,c,h*w) # b,c,hw + w_ = torch.bmm(q,k) # b,hw,hw w[b,i,j]=sum_c q[b,i,c]k[b,c,j] + w_ = w_ * (int(c)**(-0.5)) + w_ = torch.nn.functional.softmax(w_, dim=2) + + # attend to values + v = v.reshape(b,c,h*w) + w_ = w_.permute(0,2,1) # b,hw,hw (first hw of k, second of q) + h_ = torch.bmm(v,w_) # b, c,hw (hw of q) h_[b,c,j] = sum_i v[b,c,i] w_[b,i,j] + h_ = h_.reshape(b,c,h,w) + + h_ = self.proj_out(h_) + + return x+h_ + +class AttnBlock1d(nn.Module): + + def __init__(self, in_channels): + super().__init__() + self.in_channels = in_channels + + self.norm = Normalize(in_channels) + self.q = torch.nn.Conv1d(in_channels, in_channels, kernel_size=1, stride=1, padding=0) + self.k = torch.nn.Conv1d(in_channels, in_channels, kernel_size=1, stride=1, padding=0) + self.v = torch.nn.Conv1d(in_channels, in_channels, kernel_size=1, stride=1, padding=0) + self.proj_out = torch.nn.Conv1d(in_channels, in_channels, kernel_size=1, stride=1, padding=0) + + def forward(self, x): + h_ = x + h_ = self.norm(h_) + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + + # compute attention + b, c, t = q.shape + q = q.permute(0, 2, 1) # b,t,c + w_ = torch.bmm(q, k) # b,t,t w[b,i,j]=sum_c q[b,i,c]k[b,c,j] + w_ = w_ * (int(c) ** (-0.5)) + w_ = torch.nn.functional.softmax(w_, dim=2) + + # attend to values + w_ = w_.permute(0, 2, 1) # b,t,t (first t of k, second of q) + h_ = torch.bmm(v, w_) # b,c,t (t of q) h_[b,c,j] = sum_i v[b,c,i] w_[b,i,j] + + h_ = self.proj_out(h_) + + return x + h_ + + +class Model(nn.Module): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels, + resolution, use_timestep=True): + super().__init__() + self.ch = ch + self.temb_ch = self.ch*4 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + + self.use_timestep = use_timestep + if self.use_timestep: + # timestep embedding + self.temb = nn.Module() + self.temb.dense = nn.ModuleList([ + torch.nn.Linear(self.ch, + self.temb_ch), + torch.nn.Linear(self.temb_ch, + self.temb_ch), + ]) + + # downsampling + self.conv_in = torch.nn.Conv2d(in_channels, + self.ch, + kernel_size=3, + stride=1, + padding=1) + + curr_res = resolution + in_ch_mult = (1,)+tuple(ch_mult) + self.down = nn.ModuleList() + for i_level in range(self.num_resolutions): + block = nn.ModuleList() + attn = nn.ModuleList() + block_in = ch*in_ch_mult[i_level] + block_out = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks): + block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(AttnBlock(block_in)) + down = nn.Module() + down.block = block + down.attn = attn + if i_level != self.num_resolutions-1: + down.downsample = Downsample(block_in, resamp_with_conv) + curr_res = curr_res // 2 + self.down.append(down) + + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + self.mid.attn_1 = AttnBlock(block_in) + self.mid.block_2 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + + # upsampling + self.up = nn.ModuleList() + for i_level in reversed(range(self.num_resolutions)): + block = nn.ModuleList() + attn = nn.ModuleList() + block_out = ch*ch_mult[i_level] + skip_in = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks+1): + if i_block == self.num_res_blocks: + skip_in = ch*in_ch_mult[i_level] + block.append(ResnetBlock(in_channels=block_in+skip_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(AttnBlock(block_in)) + up = nn.Module() + up.block = block + up.attn = attn + if i_level != 0: + up.upsample = Upsample(block_in, resamp_with_conv) + curr_res = curr_res * 2 + self.up.insert(0, up) # prepend to get consistent order + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + out_ch, + kernel_size=3, + stride=1, + padding=1) + + + def forward(self, x, t=None): + #assert x.shape[2] == x.shape[3] == self.resolution + + if self.use_timestep: + # timestep embedding + assert t is not None + temb = get_timestep_embedding(t, self.ch) + temb = self.temb.dense[0](temb) + temb = nonlinearity(temb) + temb = self.temb.dense[1](temb) + else: + temb = None + + # downsampling + hs = [self.conv_in(x)] + for i_level in range(self.num_resolutions): + for i_block in range(self.num_res_blocks): + h = self.down[i_level].block[i_block](hs[-1], temb) + if len(self.down[i_level].attn) > 0: + h = self.down[i_level].attn[i_block](h) + hs.append(h) + if i_level != self.num_resolutions-1: + hs.append(self.down[i_level].downsample(hs[-1])) + + # middle + h = hs[-1] + h = self.mid.block_1(h, temb) + h = self.mid.attn_1(h) + h = self.mid.block_2(h, temb) + + # upsampling + for i_level in reversed(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks+1): + h = self.up[i_level].block[i_block]( + torch.cat([h, hs.pop()], dim=1), temb) + if len(self.up[i_level].attn) > 0: + h = self.up[i_level].attn[i_block](h) + if i_level != 0: + h = self.up[i_level].upsample(h) + + # end + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + return h + + +class Encoder(nn.Module): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels, + resolution, z_channels, double_z=True, **ignore_kwargs): + super().__init__() + self.ch = ch + self.temb_ch = 0 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + + # downsampling + self.conv_in = torch.nn.Conv2d(in_channels, + self.ch, + kernel_size=3, + stride=1, + padding=1) + + curr_res = resolution + in_ch_mult = (1,)+tuple(ch_mult) + self.down = nn.ModuleList() + for i_level in range(self.num_resolutions): + block = nn.ModuleList() + attn = nn.ModuleList() + block_in = ch*in_ch_mult[i_level] + block_out = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks): + block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(AttnBlock(block_in)) + down = nn.Module() + down.block = block + down.attn = attn + if i_level != self.num_resolutions-1: + down.downsample = Downsample(block_in, resamp_with_conv) + curr_res = curr_res // 2 + self.down.append(down) + + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + self.mid.attn_1 = AttnBlock(block_in) + self.mid.block_2 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + 2*z_channels if double_z else z_channels, + kernel_size=3, + stride=1, + padding=1) + + + def forward(self, x): + #assert x.shape[2] == x.shape[3] == self.resolution, "{}, {}, {}".format(x.shape[2], x.shape[3], self.resolution) + + # timestep embedding + temb = None + + # downsampling + hs = [self.conv_in(x)] + for i_level in range(self.num_resolutions): + for i_block in range(self.num_res_blocks): + h = self.down[i_level].block[i_block](hs[-1], temb) + if len(self.down[i_level].attn) > 0: + h = self.down[i_level].attn[i_block](h) + hs.append(h) + if i_level != self.num_resolutions-1: + hs.append(self.down[i_level].downsample(hs[-1])) + + # middle + h = hs[-1] + h = self.mid.block_1(h, temb) + h = self.mid.attn_1(h) + h = self.mid.block_2(h, temb) + + # end + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + return h + +class Encoder1d(Encoder): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels, + resolution, z_channels, double_z=True, **ignore_kwargs): + super().__init__(ch=ch, out_ch=out_ch, ch_mult=ch_mult, num_res_blocks=num_res_blocks, + attn_resolutions=attn_resolutions, dropout=dropout, + resamp_with_conv=resamp_with_conv, + in_channels=in_channels, resolution=resolution, z_channels=z_channels, + double_z=double_z, **ignore_kwargs) + self.ch = ch + self.temb_ch = 0 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + + # downsampling + self.conv_in = torch.nn.Conv1d(in_channels, + self.ch, + kernel_size=3, + stride=1, + padding=1) + + curr_res = resolution + in_ch_mult = (1,)+tuple(ch_mult) + self.down = nn.ModuleList() + for i_level in range(self.num_resolutions): + block = nn.ModuleList() + attn = nn.ModuleList() + block_in = ch*in_ch_mult[i_level] + block_out = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks): + block.append(ResnetBlock1d(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(AttnBlock1d(block_in)) + down = nn.Module() + down.block = block + down.attn = attn + if i_level != self.num_resolutions-1: + down.downsample = Downsample1d(block_in, resamp_with_conv) + curr_res = curr_res // 2 + self.down.append(down) + + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock1d(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + self.mid.attn_1 = AttnBlock1d(block_in) + self.mid.block_2 = ResnetBlock1d(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv1d(block_in, + 2*z_channels if double_z else z_channels, + kernel_size=3, + stride=1, + padding=1) + + +class Decoder(nn.Module): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels, + resolution, z_channels, give_pre_end=False, **ignorekwargs): + super().__init__() + self.ch = ch + self.temb_ch = 0 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + self.give_pre_end = give_pre_end + + # compute in_ch_mult, block_in and curr_res at lowest res + in_ch_mult = (1,)+tuple(ch_mult) + block_in = ch*ch_mult[self.num_resolutions-1] + curr_res = resolution // 2**(self.num_resolutions-1) + # self.z_shape = (1,z_channels,curr_res,curr_res) + # print("Working with z of shape {} = {} dimensions.".format( + # self.z_shape, np.prod(self.z_shape))) + + # z to block_in + self.conv_in = torch.nn.Conv2d(z_channels, + block_in, + kernel_size=3, + stride=1, + padding=1) + + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + self.mid.attn_1 = AttnBlock(block_in) + self.mid.block_2 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + + # upsampling + self.up = nn.ModuleList() + for i_level in reversed(range(self.num_resolutions)): + block = nn.ModuleList() + attn = nn.ModuleList() + block_out = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks+1): + block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(AttnBlock(block_in)) + up = nn.Module() + up.block = block + up.attn = attn + if i_level != 0: + up.upsample = Upsample(block_in, resamp_with_conv) + curr_res = curr_res * 2 + self.up.insert(0, up) # prepend to get consistent order + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + out_ch, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, z): + #assert z.shape[1:] == self.z_shape[1:] + self.last_z_shape = z.shape + + # timestep embedding + temb = None + + # z to block_in + h = self.conv_in(z) + + # middle + h = self.mid.block_1(h, temb) + h = self.mid.attn_1(h) + h = self.mid.block_2(h, temb) + + # upsampling + for i_level in reversed(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks+1): + h = self.up[i_level].block[i_block](h, temb) + if len(self.up[i_level].attn) > 0: + h = self.up[i_level].attn[i_block](h) + if i_level != 0: + h = self.up[i_level].upsample(h) + + # end + if self.give_pre_end: + return h + + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + return h + +class Decoder1d(Decoder): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels, + resolution, z_channels, give_pre_end=False, **ignorekwargs): + super().__init__(ch=ch, out_ch=out_ch, ch_mult=ch_mult, num_res_blocks=num_res_blocks, + attn_resolutions=attn_resolutions, dropout=dropout, + resamp_with_conv=resamp_with_conv, + in_channels=in_channels, resolution=resolution, z_channels=z_channels, + give_pre_end=give_pre_end, **ignorekwargs) + self.ch = ch + self.temb_ch = 0 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + self.give_pre_end = give_pre_end + + # compute in_ch_mult, block_in and curr_res at lowest res + in_ch_mult = (1,) + tuple(ch_mult) + block_in = ch * ch_mult[self.num_resolutions-1] + curr_res = resolution // 2**(self.num_resolutions-1) + # self.z_shape = (1,z_channels,curr_res,curr_res) + # print("Working with z of shape {} = {} dimensions.".format( + # self.z_shape, np.prod(self.z_shape))) + + # z to block_in + self.conv_in = torch.nn.Conv1d(z_channels, block_in, kernel_size=3, stride=1, padding=1) + + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock1d(in_channels=block_in, out_channels=block_in, + temb_channels=self.temb_ch, dropout=dropout) + self.mid.attn_1 = AttnBlock1d(block_in) + self.mid.block_2 = ResnetBlock1d(in_channels=block_in, out_channels=block_in, + temb_channels=self.temb_ch, dropout=dropout) + + # upsampling + self.up = nn.ModuleList() + for i_level in reversed(range(self.num_resolutions)): + block = nn.ModuleList() + attn = nn.ModuleList() + block_out = ch * ch_mult[i_level] + for i_block in range(self.num_res_blocks+1): + block.append(ResnetBlock1d(in_channels=block_in, out_channels=block_out, + temb_channels=self.temb_ch, dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(AttnBlock1d(block_in)) + up = nn.Module() + up.block = block + up.attn = attn + if i_level != 0: + up.upsample = Upsample1d(block_in, resamp_with_conv) + curr_res = curr_res * 2 + self.up.insert(0, up) # prepend to get consistent order + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv1d(block_in, out_ch, kernel_size=3, stride=1, padding=1) + + +class VUNet(nn.Module): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, + in_channels, c_channels, + resolution, z_channels, use_timestep=False, **ignore_kwargs): + super().__init__() + self.ch = ch + self.temb_ch = self.ch*4 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + + self.use_timestep = use_timestep + if self.use_timestep: + # timestep embedding + self.temb = nn.Module() + self.temb.dense = nn.ModuleList([ + torch.nn.Linear(self.ch, + self.temb_ch), + torch.nn.Linear(self.temb_ch, + self.temb_ch), + ]) + + # downsampling + self.conv_in = torch.nn.Conv2d(c_channels, + self.ch, + kernel_size=3, + stride=1, + padding=1) + + curr_res = resolution + in_ch_mult = (1,)+tuple(ch_mult) + self.down = nn.ModuleList() + for i_level in range(self.num_resolutions): + block = nn.ModuleList() + attn = nn.ModuleList() + block_in = ch*in_ch_mult[i_level] + block_out = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks): + block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(AttnBlock(block_in)) + down = nn.Module() + down.block = block + down.attn = attn + if i_level != self.num_resolutions-1: + down.downsample = Downsample(block_in, resamp_with_conv) + curr_res = curr_res // 2 + self.down.append(down) + + self.z_in = torch.nn.Conv2d(z_channels, + block_in, + kernel_size=1, + stride=1, + padding=0) + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=2*block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + self.mid.attn_1 = AttnBlock(block_in) + self.mid.block_2 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + + # upsampling + self.up = nn.ModuleList() + for i_level in reversed(range(self.num_resolutions)): + block = nn.ModuleList() + attn = nn.ModuleList() + block_out = ch*ch_mult[i_level] + skip_in = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks+1): + if i_block == self.num_res_blocks: + skip_in = ch*in_ch_mult[i_level] + block.append(ResnetBlock(in_channels=block_in+skip_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(AttnBlock(block_in)) + up = nn.Module() + up.block = block + up.attn = attn + if i_level != 0: + up.upsample = Upsample(block_in, resamp_with_conv) + curr_res = curr_res * 2 + self.up.insert(0, up) # prepend to get consistent order + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + out_ch, + kernel_size=3, + stride=1, + padding=1) + + + def forward(self, x, z): + #assert x.shape[2] == x.shape[3] == self.resolution + + if self.use_timestep: + # timestep embedding + assert t is not None + temb = get_timestep_embedding(t, self.ch) + temb = self.temb.dense[0](temb) + temb = nonlinearity(temb) + temb = self.temb.dense[1](temb) + else: + temb = None + + # downsampling + hs = [self.conv_in(x)] + for i_level in range(self.num_resolutions): + for i_block in range(self.num_res_blocks): + h = self.down[i_level].block[i_block](hs[-1], temb) + if len(self.down[i_level].attn) > 0: + h = self.down[i_level].attn[i_block](h) + hs.append(h) + if i_level != self.num_resolutions-1: + hs.append(self.down[i_level].downsample(hs[-1])) + + # middle + h = hs[-1] + z = self.z_in(z) + h = torch.cat((h,z),dim=1) + h = self.mid.block_1(h, temb) + h = self.mid.attn_1(h) + h = self.mid.block_2(h, temb) + + # upsampling + for i_level in reversed(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks+1): + h = self.up[i_level].block[i_block]( + torch.cat([h, hs.pop()], dim=1), temb) + if len(self.up[i_level].attn) > 0: + h = self.up[i_level].attn[i_block](h) + if i_level != 0: + h = self.up[i_level].upsample(h) + + # end + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + return h + + +class SimpleDecoder(nn.Module): + def __init__(self, in_channels, out_channels, *args, **kwargs): + super().__init__() + self.model = nn.ModuleList([nn.Conv2d(in_channels, in_channels, 1), + ResnetBlock(in_channels=in_channels, + out_channels=2 * in_channels, + temb_channels=0, dropout=0.0), + ResnetBlock(in_channels=2 * in_channels, + out_channels=4 * in_channels, + temb_channels=0, dropout=0.0), + ResnetBlock(in_channels=4 * in_channels, + out_channels=2 * in_channels, + temb_channels=0, dropout=0.0), + nn.Conv2d(2*in_channels, in_channels, 1), + Upsample(in_channels, with_conv=True)]) + # end + self.norm_out = Normalize(in_channels) + self.conv_out = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + for i, layer in enumerate(self.model): + if i in [1,2,3]: + x = layer(x, None) + else: + x = layer(x) + + h = self.norm_out(x) + h = nonlinearity(h) + x = self.conv_out(h) + return x + + +class UpsampleDecoder(nn.Module): + def __init__(self, in_channels, out_channels, ch, num_res_blocks, resolution, + ch_mult=(2,2), dropout=0.0): + super().__init__() + # upsampling + self.temb_ch = 0 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + block_in = in_channels + curr_res = resolution // 2 ** (self.num_resolutions - 1) + self.res_blocks = nn.ModuleList() + self.upsample_blocks = nn.ModuleList() + for i_level in range(self.num_resolutions): + res_block = [] + block_out = ch * ch_mult[i_level] + for i_block in range(self.num_res_blocks + 1): + res_block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + self.res_blocks.append(nn.ModuleList(res_block)) + if i_level != self.num_resolutions - 1: + self.upsample_blocks.append(Upsample(block_in, True)) + curr_res = curr_res * 2 + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + out_channels, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + # upsampling + h = x + for k, i_level in enumerate(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks + 1): + h = self.res_blocks[i_level][i_block](h, None) + if i_level != self.num_resolutions - 1: + h = self.upsample_blocks[k](h) + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + return h + + +if __name__ == '__main__': + ddconfig = { + 'ch': 128, + 'num_res_blocks': 2, + 'dropout': 0.0, + 'z_channels': 256, + 'double_z': False, + } + + # Audio example ## + ddconfig['in_channels'] = 1 + ddconfig['resolution'] = 848 + ddconfig['attn_resolutions'] = [53] + ddconfig['ch_mult'] = [1, 1, 2, 2, 4] + ddconfig['out_ch'] = 1 + # input + inputs = torch.rand(4, 1, 80, 848) + print('Input:', inputs.shape) + # Encoder + encoder = Encoder(**ddconfig) + enc_outs = encoder(inputs) + print('Encoder out:', enc_outs.shape) + # Decoder + decoder = Decoder(**ddconfig) + quant_outs = torch.rand(4, 256, 5, 53) + dec_outs = decoder(quant_outs) + print('Decoder out:', dec_outs.shape) diff --git a/foleycrafter/models/specvqgan/modules/discriminator/model.py b/foleycrafter/models/specvqgan/modules/discriminator/model.py new file mode 100644 index 0000000000000000000000000000000000000000..5263368a5e74d9d07840399469ca12a54e7fecbc --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/discriminator/model.py @@ -0,0 +1,295 @@ +import functools +import torch.nn as nn + + +class ActNorm(nn.Module): + def __init__(self, num_features, logdet=False, affine=True, + allow_reverse_init=False): + assert affine + super().__init__() + self.logdet = logdet + self.loc = nn.Parameter(torch.zeros(1, num_features, 1, 1)) + self.scale = nn.Parameter(torch.ones(1, num_features, 1, 1)) + self.allow_reverse_init = allow_reverse_init + + self.register_buffer('initialized', torch.tensor(0, dtype=torch.uint8)) + + def initialize(self, input): + with torch.no_grad(): + flatten = input.permute(1, 0, 2, 3).contiguous().view(input.shape[1], -1) + mean = ( + flatten.mean(1) + .unsqueeze(1) + .unsqueeze(2) + .unsqueeze(3) + .permute(1, 0, 2, 3) + ) + std = ( + flatten.std(1) + .unsqueeze(1) + .unsqueeze(2) + .unsqueeze(3) + .permute(1, 0, 2, 3) + ) + + self.loc.data.copy_(-mean) + self.scale.data.copy_(1 / (std + 1e-6)) + + def forward(self, input, reverse=False): + if reverse: + return self.reverse(input) + if len(input.shape) == 2: + input = input[:, :, None, None] + squeeze = True + else: + squeeze = False + + _, _, height, width = input.shape + + if self.training and self.initialized.item() == 0: + self.initialize(input) + self.initialized.fill_(1) + + h = self.scale * (input + self.loc) + + if squeeze: + h = h.squeeze(-1).squeeze(-1) + + if self.logdet: + log_abs = torch.log(torch.abs(self.scale)) + logdet = height * width * torch.sum(log_abs) + logdet = logdet * torch.ones(input.shape[0]).to(input) + return h, logdet + + return h + + def reverse(self, output): + if self.training and self.initialized.item() == 0: + if not self.allow_reverse_init: + raise RuntimeError( + "Initializing ActNorm in reverse direction is " + "disabled by default. Use allow_reverse_init=True to enable." + ) + else: + self.initialize(output) + self.initialized.fill_(1) + + if len(output.shape) == 2: + output = output[:, :, None, None] + squeeze = True + else: + squeeze = False + + h = output / self.scale - self.loc + + if squeeze: + h = h.squeeze(-1).squeeze(-1) + return h + +def weights_init(m): + classname = m.__class__.__name__ + if classname.find('Conv') != -1: + nn.init.normal_(m.weight.data, 0.0, 0.02) + elif classname.find('BatchNorm') != -1: + nn.init.normal_(m.weight.data, 1.0, 0.02) + nn.init.constant_(m.bias.data, 0) + + +class NLayerDiscriminator(nn.Module): + """Defines a PatchGAN discriminator as in Pix2Pix + --> see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/networks.py + """ + def __init__(self, input_nc=3, ndf=64, n_layers=3, use_actnorm=False): + """Construct a PatchGAN discriminator + Parameters: + input_nc (int) -- the number of channels in input images + ndf (int) -- the number of filters in the last conv layer + n_layers (int) -- the number of conv layers in the discriminator + norm_layer -- normalization layer + """ + super(NLayerDiscriminator, self).__init__() + if not use_actnorm: + norm_layer = nn.BatchNorm2d + else: + norm_layer = ActNorm + if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm2d has affine parameters + use_bias = norm_layer.func != nn.BatchNorm2d + else: + use_bias = norm_layer != nn.BatchNorm2d + + kw = 4 + padw = 1 + sequence = [nn.Conv2d(input_nc, ndf, kernel_size=kw, stride=2, padding=padw), nn.LeakyReLU(0.2, True)] + nf_mult = 1 + nf_mult_prev = 1 + for n in range(1, n_layers): # gradually increase the number of filters + nf_mult_prev = nf_mult + nf_mult = min(2 ** n, 8) + sequence += [ + nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=2, padding=padw, bias=use_bias), + norm_layer(ndf * nf_mult), + nn.LeakyReLU(0.2, True) + ] + + nf_mult_prev = nf_mult + nf_mult = min(2 ** n_layers, 8) + sequence += [ + nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=1, padding=padw, bias=use_bias), + norm_layer(ndf * nf_mult), + nn.LeakyReLU(0.2, True) + ] + # output 1 channel prediction map + sequence += [nn.Conv2d(ndf * nf_mult, 1, kernel_size=kw, stride=1, padding=padw)] + self.main = nn.Sequential(*sequence) + + def forward(self, input): + """Standard forward.""" + return self.main(input) + +class NLayerDiscriminator1dFeats(NLayerDiscriminator): + """Defines a PatchGAN discriminator as in Pix2Pix + --> see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/networks.py + """ + def __init__(self, input_nc=3, ndf=64, n_layers=3, use_actnorm=False): + """Construct a PatchGAN discriminator + Parameters: + input_nc (int) -- the number of channels in input feats + ndf (int) -- the number of filters in the last conv layer + n_layers (int) -- the number of conv layers in the discriminator + norm_layer -- normalization layer + """ + super().__init__(input_nc=input_nc, ndf=64, n_layers=n_layers, use_actnorm=use_actnorm) + + if not use_actnorm: + norm_layer = nn.BatchNorm1d + else: + norm_layer = ActNorm + if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm has affine parameters + use_bias = norm_layer.func != nn.BatchNorm1d + else: + use_bias = norm_layer != nn.BatchNorm1d + + kw = 4 + padw = 1 + sequence = [nn.Conv1d(input_nc, input_nc//2, kernel_size=kw, stride=2, padding=padw), nn.LeakyReLU(0.2, True)] + nf_mult = input_nc//2 + nf_mult_prev = 1 + for n in range(1, n_layers): # gradually decrease the number of filters + nf_mult_prev = nf_mult + nf_mult = max(nf_mult_prev // (2 ** n), 8) + sequence += [ + nn.Conv1d(nf_mult_prev, nf_mult, kernel_size=kw, stride=2, padding=padw, bias=use_bias), + norm_layer(nf_mult), + nn.LeakyReLU(0.2, True) + ] + + nf_mult_prev = nf_mult + nf_mult = max(nf_mult_prev // (2 ** n), 8) + sequence += [ + nn.Conv1d(nf_mult_prev, nf_mult, kernel_size=kw, stride=1, padding=padw, bias=use_bias), + norm_layer(nf_mult), + nn.LeakyReLU(0.2, True) + ] + nf_mult_prev = nf_mult + nf_mult = max(nf_mult_prev // (2 ** n), 8) + sequence += [ + nn.Conv1d(nf_mult_prev, nf_mult, kernel_size=kw, stride=1, padding=padw, bias=use_bias), + norm_layer(nf_mult), + nn.LeakyReLU(0.2, True) + ] + # output 1 channel prediction map + sequence += [nn.Conv1d(nf_mult, 1, kernel_size=kw, stride=1, padding=padw)] + self.main = nn.Sequential(*sequence) + + +class NLayerDiscriminator1dSpecs(NLayerDiscriminator): + """Defines a PatchGAN discriminator as in Pix2Pix + --> see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/networks.py + """ + def __init__(self, input_nc=80, ndf=64, n_layers=3, use_actnorm=False): + """Construct a PatchGAN discriminator + Parameters: + input_nc (int) -- the number of channels in input specs + ndf (int) -- the number of filters in the last conv layer + n_layers (int) -- the number of conv layers in the discriminator + norm_layer -- normalization layer + """ + super().__init__(input_nc=input_nc, ndf=64, n_layers=n_layers, use_actnorm=use_actnorm) + + if not use_actnorm: + norm_layer = nn.BatchNorm1d + else: + norm_layer = ActNorm + if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm has affine parameters + use_bias = norm_layer.func != nn.BatchNorm1d + else: + use_bias = norm_layer != nn.BatchNorm1d + + kw = 4 + padw = 1 + sequence = [nn.Conv1d(input_nc, ndf, kernel_size=kw, stride=2, padding=padw), nn.LeakyReLU(0.2, True)] + nf_mult = 1 + nf_mult_prev = 1 + for n in range(1, n_layers): # gradually decrease the number of filters + nf_mult_prev = nf_mult + nf_mult = min(2 ** n, 8) + sequence += [ + nn.Conv1d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=2, padding=padw, bias=use_bias), + norm_layer(ndf * nf_mult), + nn.LeakyReLU(0.2, True) + ] + + nf_mult_prev = nf_mult + nf_mult = min(2 ** n_layers, 8) + sequence += [ + nn.Conv1d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=1, padding=padw, bias=use_bias), + norm_layer(ndf * nf_mult), + nn.LeakyReLU(0.2, True) + ] + # output 1 channel prediction map + sequence += [nn.Conv1d(ndf * nf_mult, 1, kernel_size=kw, stride=1, padding=padw)] + self.main = nn.Sequential(*sequence) + + def forward(self, input): + """Standard forward.""" + # (B, C, L) + input = input.squeeze(1) + input = self.main(input) + return input + + +if __name__ == '__main__': + import torch + + ## FEATURES + disc_in_channels = 2048 + disc_num_layers = 2 + use_actnorm = False + disc_ndf = 64 + discriminator = NLayerDiscriminator1dFeats(input_nc=disc_in_channels, n_layers=disc_num_layers, + use_actnorm=use_actnorm, ndf=disc_ndf).apply(weights_init) + inputs = torch.rand((6, 2048, 212)) + outputs = discriminator(inputs) + print(outputs.shape) + + ## AUDIO + disc_in_channels = 1 + disc_num_layers = 3 + use_actnorm = False + disc_ndf = 64 + discriminator = NLayerDiscriminator(input_nc=disc_in_channels, n_layers=disc_num_layers, + use_actnorm=use_actnorm, ndf=disc_ndf).apply(weights_init) + inputs = torch.rand((6, 1, 80, 848)) + outputs = discriminator(inputs) + print(outputs.shape) + + ## IMAGE + disc_in_channels = 3 + disc_num_layers = 3 + use_actnorm = False + disc_ndf = 64 + discriminator = NLayerDiscriminator(input_nc=disc_in_channels, n_layers=disc_num_layers, + use_actnorm=use_actnorm, ndf=disc_ndf).apply(weights_init) + inputs = torch.rand((6, 3, 256, 256)) + outputs = discriminator(inputs) + print(outputs.shape) diff --git a/foleycrafter/models/specvqgan/modules/losses/__init__.py b/foleycrafter/models/specvqgan/modules/losses/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..533c5aa92c87f32fd5676e02463c703b22130f73 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/__init__.py @@ -0,0 +1,7 @@ +from foleycrafter.models.specvqgan.modules.losses.vqperceptual import DummyLoss + +# relative imports pain +import os +import sys +path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'vggishish') +sys.path.append(path) diff --git a/foleycrafter/models/specvqgan/modules/losses/lpaps.py b/foleycrafter/models/specvqgan/modules/losses/lpaps.py new file mode 100644 index 0000000000000000000000000000000000000000..4e2a3f861f8ae1024da40c71f57a5ddd5098cfab --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/lpaps.py @@ -0,0 +1,152 @@ +""" + Based on https://github.com/CompVis/taming-transformers/blob/52720829/taming/modules/losses/lpips.py + Adapted for spectrograms by Vladimir Iashin (v-iashin) +""" +from collections import namedtuple + +import numpy as np +import torch +import torch.nn as nn + +import sys +sys.path.insert(0, '.') # nopep8 +from foleycrafter.models.specvqgan.modules.losses.vggishish.model import VGGishish +from foleycrafter.models.specvqgan.util import get_ckpt_path + + +class LPAPS(nn.Module): + # Learned perceptual metric + def __init__(self, use_dropout=True): + super().__init__() + self.scaling_layer = ScalingLayer() + self.chns = [64, 128, 256, 512, 512] # vggish16 features + self.net = vggishish16(pretrained=True, requires_grad=False) + self.lin0 = NetLinLayer(self.chns[0], use_dropout=use_dropout) + self.lin1 = NetLinLayer(self.chns[1], use_dropout=use_dropout) + self.lin2 = NetLinLayer(self.chns[2], use_dropout=use_dropout) + self.lin3 = NetLinLayer(self.chns[3], use_dropout=use_dropout) + self.lin4 = NetLinLayer(self.chns[4], use_dropout=use_dropout) + self.load_from_pretrained() + for param in self.parameters(): + param.requires_grad = False + + def load_from_pretrained(self, name="vggishish_lpaps"): + ckpt = get_ckpt_path(name, "specvqgan/modules/autoencoder/lpaps") + self.load_state_dict(torch.load(ckpt, map_location=torch.device("cpu")), strict=False) + print("loaded pretrained LPAPS loss from {}".format(ckpt)) + + @classmethod + def from_pretrained(cls, name="vggishish_lpaps"): + if name != "vggishish_lpaps": + raise NotImplementedError + model = cls() + ckpt = get_ckpt_path(name) + model.load_state_dict(torch.load(ckpt, map_location=torch.device("cpu")), strict=False) + return model + + def forward(self, input, target): + in0_input, in1_input = (self.scaling_layer(input), self.scaling_layer(target)) + outs0, outs1 = self.net(in0_input), self.net(in1_input) + feats0, feats1, diffs = {}, {}, {} + lins = [self.lin0, self.lin1, self.lin2, self.lin3, self.lin4] + for kk in range(len(self.chns)): + feats0[kk], feats1[kk] = normalize_tensor(outs0[kk]), normalize_tensor(outs1[kk]) + diffs[kk] = (feats0[kk] - feats1[kk]) ** 2 + + res = [spatial_average(lins[kk].model(diffs[kk]), keepdim=True) for kk in range(len(self.chns))] + val = res[0] + for l in range(1, len(self.chns)): + val += res[l] + return val + +class ScalingLayer(nn.Module): + def __init__(self): + super(ScalingLayer, self).__init__() + # we are gonna use get_ckpt_path to donwload the stats as well + stat_path = get_ckpt_path('vggishish_mean_std_melspec_10s_22050hz', 'specvqgan/modules/autoencoder/lpaps') + # if for images we normalize on the channel dim, in spectrogram we will norm on frequency dimension + means, stds = np.loadtxt(stat_path, dtype=np.float32).T + # the normalization in means and stds are given for [0, 1], but specvqgan expects [-1, 1]: + means = 2 * means - 1 + stds = 2 * stds + # input is expected to be (B, 1, F, T) + self.register_buffer('shift', torch.from_numpy(means)[None, None, :, None]) + self.register_buffer('scale', torch.from_numpy(stds)[None, None, :, None]) + + def forward(self, inp): + return (inp - self.shift) / self.scale + + +class NetLinLayer(nn.Module): + """ A single linear layer which does a 1x1 conv """ + def __init__(self, chn_in, chn_out=1, use_dropout=False): + super(NetLinLayer, self).__init__() + layers = [nn.Dropout(), ] if (use_dropout) else [] + layers += [nn.Conv2d(chn_in, chn_out, 1, stride=1, padding=0, bias=False), ] + self.model = nn.Sequential(*layers) + +class vggishish16(torch.nn.Module): + def __init__(self, requires_grad=False, pretrained=True): + super().__init__() + vgg_pretrained_features = self.vggishish16(pretrained=pretrained).features + self.slice1 = torch.nn.Sequential() + self.slice2 = torch.nn.Sequential() + self.slice3 = torch.nn.Sequential() + self.slice4 = torch.nn.Sequential() + self.slice5 = torch.nn.Sequential() + self.N_slices = 5 + for x in range(4): + self.slice1.add_module(str(x), vgg_pretrained_features[x]) + for x in range(4, 9): + self.slice2.add_module(str(x), vgg_pretrained_features[x]) + for x in range(9, 16): + self.slice3.add_module(str(x), vgg_pretrained_features[x]) + for x in range(16, 23): + self.slice4.add_module(str(x), vgg_pretrained_features[x]) + for x in range(23, 30): + self.slice5.add_module(str(x), vgg_pretrained_features[x]) + if not requires_grad: + for param in self.parameters(): + param.requires_grad = False + + def forward(self, X): + h = self.slice1(X) + h_relu1_2 = h + h = self.slice2(h) + h_relu2_2 = h + h = self.slice3(h) + h_relu3_3 = h + h = self.slice4(h) + h_relu4_3 = h + h = self.slice5(h) + h_relu5_3 = h + vgg_outputs = namedtuple("VggOutputs", ['relu1_2', 'relu2_2', 'relu3_3', 'relu4_3', 'relu5_3']) + out = vgg_outputs(h_relu1_2, h_relu2_2, h_relu3_3, h_relu4_3, h_relu5_3) + return out + + def vggishish16(self, pretrained: bool = True) -> VGGishish: + # loading vggishish pretrained on vggsound + num_classes_vggsound = 309 + conv_layers = [64, 64, 'MP', 128, 128, 'MP', 256, 256, 256, 'MP', 512, 512, 512, 'MP', 512, 512, 512] + model = VGGishish(conv_layers, use_bn=False, num_classes=num_classes_vggsound) + if pretrained: + ckpt_path = get_ckpt_path('vggishish_lpaps', "specvqgan/modules/autoencoder/lpaps") + ckpt = torch.load(ckpt_path, map_location=torch.device("cpu")) + model.load_state_dict(ckpt, strict=False) + return model + +def normalize_tensor(x, eps=1e-10): + norm_factor = torch.sqrt(torch.sum(x**2, dim=1, keepdim=True)) + return x / (norm_factor+eps) + +def spatial_average(x, keepdim=True): + return x.mean([2, 3], keepdim=keepdim) + + +if __name__ == '__main__': + inputs = torch.rand((16, 1, 80, 848)) + reconstructions = torch.rand((16, 1, 80, 848)) + lpips = LPAPS().eval() + loss_p = lpips(inputs.contiguous(), reconstructions.contiguous()) + # (16, 1, 1, 1) + print(loss_p.shape) diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/melception.yaml b/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/melception.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4c0316968a3e779804223d33e25f4574bea75392 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/melception.yaml @@ -0,0 +1,24 @@ +seed: 1337 +log_code_state: True +# patterns to ignore when backing up the code folder +patterns_to_ignore: ['logs', '.git', '__pycache__', 'data', 'checkpoints', '*.pt'] + +# data: +mels_path: '/home/nvme/data/vggsound/features/melspec_10s_22050hz/' +spec_shape: [80, 860] +cropped_size: [80, 848] +random_crop: False + +# train: +device: 'cuda:0' +batch_size: 8 +num_workers: 0 +optimizer: adam +betas: [0.9, 0.999] +momentum: 0.9 +learning_rate: 3e-4 +weight_decay: 0 +num_epochs: 100 +patience: 3 +logdir: './logs' +cls_weights_in_loss: False diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish.yaml b/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f97359658fe257f995037e17b66244879a630498 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish.yaml @@ -0,0 +1,34 @@ +seed: 1337 +log_code_state: True +# patterns to ignore when backing up the code folder +patterns_to_ignore: ['logs', '.git', '__pycache__'] + +# data: +mels_path: '/home/nvme/data/vggsound/features/melspec_10s_22050hz/' +spec_shape: [80, 860] +cropped_size: [80, 848] +random_crop: False + +# model: +# original vgg family except for MP is missing at the end +# 'vggish': [64, 'MP', 128, 'MP', 256, 256, 'MP', 512, 512] +# 'vgg11': [64, 'MP', 128, 'MP', 256, 256, 'MP', 512, 512, 'MP', 512, 512], +# 'vgg13': [64, 64, 'MP', 128, 128, 'MP', 256, 256, 'MP', 512, 512, 'MP', 512, 512], +# 'vgg16': [64, 64, 'MP', 128, 128, 'MP', 256, 256, 256, 'MP', 512, 512, 512, 'MP', 512, 512, 512], +# 'vgg19': [64, 64, 'MP', 128, 128, 'MP', 256, 256, 256, 256, 'MP', 512, 512, 512, 512, 'MP', 512, 512, 512, 512], +conv_layers: [64, 64, 'MP', 128, 128, 'MP', 256, 256, 256, 'MP', 512, 512, 512, 'MP', 512, 512, 512] +use_bn: False + +# train: +device: 'cuda:0' +batch_size: 32 +num_workers: 0 +optimizer: adam +betas: [0.9, 0.999] +momentum: 0.9 +learning_rate: 3e-4 +weight_decay: 0.0001 +num_epochs: 100 +patience: 3 +logdir: './logs' +cls_weights_in_loss: False diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish_gh.yaml b/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish_gh.yaml new file mode 100644 index 0000000000000000000000000000000000000000..efa5f147cf88d1760f7004a7bea7f86902e7cc47 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish_gh.yaml @@ -0,0 +1,25 @@ +seed: 1337 +log_code_state: True +patterns_to_ignore: ['logs', '.git', '__pycache__'] + +mels_path: '/home/duyxxd/SpecVQGAN/data/greatesthit/melspec_10s_22050hz' +batch_size: 32 +num_workers: 8 +device: 'cuda:0' +conv_layers: [64, 64, 'MP', 128, 128, 'MP', 256, 256, 256, 'MP', 512, 512, 512, 'MP', 512, 512, 512] +use_bn: False +optimizer: adam +learning_rate: 1e-4 +betas: [0.9, 0.999] +cropped_size: [80, 160] +momentum: 0.9 +weight_decay: 1e-4 +cls_weights_in_loss: False +num_epochs: 100 +patience: 20 +logdir: '/home/duyxxd/SpecVQGAN/logs' +exp_name: 'mix' +action_only: False +material_only: False + +load_model: /home/duyxxd/SpecVQGAN/logs/vggishish16.pt \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish_gh_action.yaml b/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish_gh_action.yaml new file mode 100644 index 0000000000000000000000000000000000000000..bd7df483cf0ff1a0a62d0f84ee852511c94e73b9 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish_gh_action.yaml @@ -0,0 +1,25 @@ +seed: 1337 +log_code_state: True +patterns_to_ignore: ['logs', '.git', '__pycache__'] + +mels_path: '/home/duyxxd/SpecVQGAN/data/greatesthit/melspec_10s_22050hz' +batch_size: 32 +num_workers: 8 +device: 'cuda:0' +conv_layers: [64, 64, 'MP', 128, 128, 'MP', 256, 256, 256, 'MP', 512, 512, 512, 'MP', 512, 512, 512] +use_bn: False +optimizer: adam +learning_rate: 1e-4 +betas: [0.9, 0.999] +cropped_size: [80, 160] +momentum: 0.9 +weight_decay: 1e-4 +cls_weights_in_loss: False +num_epochs: 20 +patience: 20 +logdir: '/home/duyxxd/SpecVQGAN/logs' +exp_name: 'action' +action_only: True +material_only: False + +load_model: /home/duyxxd/SpecVQGAN/logs/vggishish16.pt \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish_gh_material.yaml b/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish_gh_material.yaml new file mode 100644 index 0000000000000000000000000000000000000000..beba550c3f850279b42308a2613a8fae59de5377 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/configs/vggish_gh_material.yaml @@ -0,0 +1,25 @@ +seed: 1337 +log_code_state: True +patterns_to_ignore: ['logs', '.git', '__pycache__'] + +mels_path: '/home/duyxxd/SpecVQGAN/data/greatesthit/melspec_10s_22050hz' +batch_size: 32 +num_workers: 8 +device: 'cuda:0' +conv_layers: [64, 64, 'MP', 128, 128, 'MP', 256, 256, 256, 'MP', 512, 512, 512, 'MP', 512, 512, 512] +use_bn: False +optimizer: adam +learning_rate: 1e-4 +betas: [0.9, 0.999] +cropped_size: [80, 160] +momentum: 0.9 +weight_decay: 1e-4 +cls_weights_in_loss: False +num_epochs: 20 +patience: 20 +logdir: '/home/duyxxd/SpecVQGAN/logs' +exp_name: 'material' +action_only: False +material_only: True + +load_model: /home/duyxxd/SpecVQGAN/logs/vggishish16.pt \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/dataset.py b/foleycrafter/models/specvqgan/modules/losses/vggishish/dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..2b9603b9f4630079b0f0712c8ef78ef09044e325 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/dataset.py @@ -0,0 +1,295 @@ +import collections +import csv +import logging +import os +import random +import math +import json +from glob import glob +from pathlib import Path + +import numpy as np +import torch +import torchvision + +logger = logging.getLogger(f'main.{__name__}') + + +class VGGSound(torch.utils.data.Dataset): + + def __init__(self, split, specs_dir, transforms=None, splits_path='./data', meta_path='./data/vggsound.csv'): + super().__init__() + self.split = split + self.specs_dir = specs_dir + self.transforms = transforms + self.splits_path = splits_path + self.meta_path = meta_path + + vggsound_meta = list(csv.reader(open(meta_path), quotechar='"')) + unique_classes = sorted(list(set(row[2] for row in vggsound_meta))) + self.label2target = {label: target for target, label in enumerate(unique_classes)} + self.target2label = {target: label for label, target in self.label2target.items()} + self.video2target = {row[0]: self.label2target[row[2]] for row in vggsound_meta} + + split_clip_ids_path = os.path.join(splits_path, f'vggsound_{split}_partial.txt') + print('&&&&&&&&&&&&&&&&', split_clip_ids_path) + if not os.path.exists(split_clip_ids_path): + self.make_split_files() + clip_ids_with_timestamp = open(split_clip_ids_path).read().splitlines() + clip_paths = [os.path.join(specs_dir, v + '_mel.npy') for v in clip_ids_with_timestamp] + self.dataset = clip_paths + # self.dataset = clip_paths[:10000] # overfit one batch + + # 'zyTX_1BXKDE_16000_26000'[:11] -> 'zyTX_1BXKDE' + vid_classes = [self.video2target[Path(path).stem[:11]] for path in self.dataset] + class2count = collections.Counter(vid_classes) + self.class_counts = torch.tensor([class2count[cls] for cls in range(len(class2count))]) + # self.sample_weights = [len(self.dataset) / class2count[self.video2target[Path(path).stem[:11]]] for path in self.dataset] + + def __getitem__(self, idx): + item = {} + + spec_path = self.dataset[idx] + # 'zyTX_1BXKDE_16000_26000' -> 'zyTX_1BXKDE' + video_name = Path(spec_path).stem[:11] + + item['input'] = np.load(spec_path) + item['input_path'] = spec_path + + # if self.split in ['train', 'valid']: + item['target'] = self.video2target[video_name] + item['label'] = self.target2label[item['target']] + + if self.transforms is not None: + item = self.transforms(item) + + return item + + def __len__(self): + return len(self.dataset) + + def make_split_files(self): + random.seed(1337) + logger.info(f'The split files do not exist @ {self.splits_path}. Calculating the new ones.') + # The downloaded videos (some went missing on YouTube and no longer available) + available_vid_paths = sorted(glob(os.path.join(self.specs_dir, '*_mel.npy'))) + logger.info(f'The number of clips available after download: {len(available_vid_paths)}') + + # original (full) train and test sets + vggsound_meta = list(csv.reader(open(self.meta_path), quotechar='"')) + train_vids = {row[0] for row in vggsound_meta if row[3] == 'train'} + test_vids = {row[0] for row in vggsound_meta if row[3] == 'test'} + logger.info(f'The number of videos in vggsound train set: {len(train_vids)}') + logger.info(f'The number of videos in vggsound test set: {len(test_vids)}') + + # class counts in test set. We would like to have the same distribution in valid + unique_classes = sorted(list(set(row[2] for row in vggsound_meta))) + label2target = {label: target for target, label in enumerate(unique_classes)} + video2target = {row[0]: label2target[row[2]] for row in vggsound_meta} + test_vid_classes = [video2target[vid] for vid in test_vids] + test_target2count = collections.Counter(test_vid_classes) + + # now given the counts from test set, sample the same count for validation and the rest leave in train + train_vids_wo_valid, valid_vids = set(), set() + for target, label in enumerate(label2target.keys()): + class_train_vids = [vid for vid in train_vids if video2target[vid] == target] + random.shuffle(class_train_vids) + count = test_target2count[target] + valid_vids.update(class_train_vids[:count]) + train_vids_wo_valid.update(class_train_vids[count:]) + + # make file with a list of available test videos (each video should contain timestamps as well) + train_i = valid_i = test_i = 0 + with open(os.path.join(self.splits_path, 'vggsound_train.txt'), 'w') as train_file, \ + open(os.path.join(self.splits_path, 'vggsound_valid.txt'), 'w') as valid_file, \ + open(os.path.join(self.splits_path, 'vggsound_test.txt'), 'w') as test_file: + for path in available_vid_paths: + path = path.replace('_mel.npy', '') + vid_name = Path(path).name + # 'zyTX_1BXKDE_16000_26000'[:11] -> 'zyTX_1BXKDE' + if vid_name[:11] in train_vids_wo_valid: + train_file.write(vid_name + '\n') + train_i += 1 + elif vid_name[:11] in valid_vids: + valid_file.write(vid_name + '\n') + valid_i += 1 + elif vid_name[:11] in test_vids: + test_file.write(vid_name + '\n') + test_i += 1 + else: + raise Exception(f'Clip {vid_name} is neither in train, valid nor test. Strange.') + + logger.info(f'Put {train_i} clips to the train set and saved it to ./data/vggsound_train.txt') + logger.info(f'Put {valid_i} clips to the valid set and saved it to ./data/vggsound_valid.txt') + logger.info(f'Put {test_i} clips to the test set and saved it to ./data/vggsound_test.txt') + + +def get_GH_data_identifier(video_name, start_idx, split='_'): + if isinstance(start_idx, str): + return video_name + split + start_idx + elif isinstance(start_idx, int): + return video_name + split + str(start_idx) + else: + raise NotImplementedError + + +class GreatestHit(torch.utils.data.Dataset): + + def __init__(self, split, spec_dir_path, spec_transform=None, L=2.0, action_only=False, + material_only=False, splits_path='/home/duyxxd/SpecVQGAN/data', + meta_path='/home/duyxxd/SpecVQGAN/data/info_r2plus1d_dim1024_15fps.json'): + super().__init__() + self.split = split + self.specs_dir = spec_dir_path + self.splits_path = splits_path + self.meta_path = meta_path + self.spec_transform = spec_transform + self.L = L + self.spec_take_first = int(math.ceil(860 * (L / 10.) / 32) * 32) + self.spec_take_first = 860 if self.spec_take_first > 860 else self.spec_take_first + self.spec_take_first = 173 + + greatesthit_meta = json.load(open(self.meta_path, 'r')) + self.video_idx2label = { + get_GH_data_identifier(greatesthit_meta['video_name'][i], greatesthit_meta['start_idx'][i]): + greatesthit_meta['hit_type'][i] for i in range(len(greatesthit_meta['video_name'])) + } + self.available_video_hit = list(self.video_idx2label.keys()) + self.video_idx2path = { + vh: os.path.join(self.specs_dir, + vh.replace('_', '_denoised_') + '_' + self.video_idx2label[vh].replace(' ', '_') +'_mel.npy') + for vh in self.available_video_hit + } + self.video_idx2idx = { + get_GH_data_identifier(greatesthit_meta['video_name'][i], greatesthit_meta['start_idx'][i]): + i for i in range(len(greatesthit_meta['video_name'])) + } + + split_clip_ids_path = os.path.join(splits_path, f'greatesthit_{split}_2.00_single_type_only.json') + if not os.path.exists(split_clip_ids_path): + raise NotImplementedError() + clip_video_hit = json.load(open(split_clip_ids_path, 'r')) + self.dataset = list(clip_video_hit.keys()) + if action_only: + self.video_idx2label = {k: v.split(' ')[1] for k, v in clip_video_hit.items()} + elif material_only: + self.video_idx2label = {k: v.split(' ')[0] for k, v in clip_video_hit.items()} + else: + self.video_idx2label = clip_video_hit + + + self.video2indexes = {} + for video_idx in self.dataset: + video, start_idx = video_idx.split('_') + if video not in self.video2indexes.keys(): + self.video2indexes[video] = [] + self.video2indexes[video].append(start_idx) + for video in self.video2indexes.keys(): + if len(self.video2indexes[video]) == 1: # given video contains only one hit + self.dataset.remove( + get_GH_data_identifier(video, self.video2indexes[video][0]) + ) + + vid_classes = list(self.video_idx2label.values()) + unique_classes = sorted(list(set(vid_classes))) + self.label2target = {label: target for target, label in enumerate(unique_classes)} + if action_only: + label2target_fix = {'hit': 0, 'scratch': 1} + elif material_only: + label2target_fix = {'carpet': 0, 'ceramic': 1, 'cloth': 2, 'dirt': 3, 'drywall': 4, 'glass': 5, 'grass': 6, 'gravel': 7, 'leaf': 8, 'metal': 9, 'paper': 10, 'plastic': 11, 'plastic-bag': 12, 'rock': 13, 'tile': 14, 'water': 15, 'wood': 16} + else: + label2target_fix = {'carpet hit': 0, 'carpet scratch': 1, 'ceramic hit': 2, 'ceramic scratch': 3, 'cloth hit': 4, 'cloth scratch': 5, 'dirt hit': 6, 'dirt scratch': 7, 'drywall hit': 8, 'drywall scratch': 9, 'glass hit': 10, 'glass scratch': 11, 'grass hit': 12, 'grass scratch': 13, 'gravel hit': 14, 'gravel scratch': 15, 'leaf hit': 16, 'leaf scratch': 17, 'metal hit': 18, 'metal scratch': 19, 'paper hit': 20, 'paper scratch': 21, 'plastic hit': 22, 'plastic scratch': 23, 'plastic-bag hit': 24, 'plastic-bag scratch': 25, 'rock hit': 26, 'rock scratch': 27, 'tile hit': 28, 'tile scratch': 29, 'water hit': 30, 'water scratch': 31, 'wood hit': 32, 'wood scratch': 33} + for k in self.label2target.keys(): + assert k in label2target_fix.keys() + self.label2target = label2target_fix + self.target2label = {target: label for label, target in self.label2target.items()} + class2count = collections.Counter(vid_classes) + self.class_counts = torch.tensor([class2count[cls] for cls in range(len(class2count))]) + print(self.label2target) + print(len(vid_classes), len(class2count), class2count) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + item = {} + + video_idx = self.dataset[idx] + spec_path = self.video_idx2path[video_idx] + spec = np.load(spec_path) # (80, 860) + + # concat spec outside dataload + item['input'] = 2 * spec - 1 # (80, 860) + item['input'] = item['input'][:, :self.spec_take_first] # (80, 173) (since 2sec audio can only generate 173) + item['file_path'] = spec_path + + item['label'] = self.video_idx2label[video_idx] + item['target'] = self.label2target[item['label']] + + if self.spec_transform is not None: + item = self.spec_transform(item) + + return item + + + +class AMT_test(torch.utils.data.Dataset): + + def __init__(self, spec_dir_path, spec_transform=None, action_only=False, material_only=False): + super().__init__() + self.specs_dir = spec_dir_path + self.spec_transform = spec_transform + self.spec_take_first = 173 + + self.dataset = sorted([os.path.join(self.specs_dir, f) for f in os.listdir(self.specs_dir)]) + if action_only: + self.label2target = {'hit': 0, 'scratch': 1} + elif material_only: + self.label2target = {'carpet': 0, 'ceramic': 1, 'cloth': 2, 'dirt': 3, 'drywall': 4, 'glass': 5, 'grass': 6, 'gravel': 7, 'leaf': 8, 'metal': 9, 'paper': 10, 'plastic': 11, 'plastic-bag': 12, 'rock': 13, 'tile': 14, 'water': 15, 'wood': 16} + else: + self.label2target = {'carpet hit': 0, 'carpet scratch': 1, 'ceramic hit': 2, 'ceramic scratch': 3, 'cloth hit': 4, 'cloth scratch': 5, 'dirt hit': 6, 'dirt scratch': 7, 'drywall hit': 8, 'drywall scratch': 9, 'glass hit': 10, 'glass scratch': 11, 'grass hit': 12, 'grass scratch': 13, 'gravel hit': 14, 'gravel scratch': 15, 'leaf hit': 16, 'leaf scratch': 17, 'metal hit': 18, 'metal scratch': 19, 'paper hit': 20, 'paper scratch': 21, 'plastic hit': 22, 'plastic scratch': 23, 'plastic-bag hit': 24, 'plastic-bag scratch': 25, 'rock hit': 26, 'rock scratch': 27, 'tile hit': 28, 'tile scratch': 29, 'water hit': 30, 'water scratch': 31, 'wood hit': 32, 'wood scratch': 33} + self.target2label = {v: k for k, v in self.label2target.items()} + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + item = {} + + spec_path = self.dataset[idx] + spec = np.load(spec_path) # (80, 860) + + # concat spec outside dataload + item['input'] = 2 * spec - 1 # (80, 860) + item['input'] = item['input'][:, :self.spec_take_first] # (80, 173) (since 2sec audio can only generate 173) + item['file_path'] = spec_path + + if self.spec_transform is not None: + item = self.spec_transform(item) + + return item + + +if __name__ == '__main__': + from transforms import Crop, StandardNormalizeAudio, ToTensor + specs_path = '/home/nvme/data/vggsound/features/melspec_10s_22050hz/' + + transforms = torchvision.transforms.transforms.Compose([ + StandardNormalizeAudio(specs_path), + ToTensor(), + Crop([80, 848]), + ]) + + datasets = { + 'train': VGGSound('train', specs_path, transforms), + 'valid': VGGSound('valid', specs_path, transforms), + 'test': VGGSound('test', specs_path, transforms), + } + + print(datasets['train'][0]) + print(datasets['valid'][0]) + print(datasets['test'][0]) + + print(datasets['train'].class_counts) + print(datasets['valid'].class_counts) + print(datasets['test'].class_counts) diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/logger.py b/foleycrafter/models/specvqgan/modules/losses/vggishish/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..a6205dec53e29b62e2901fd899fcf02ee0eb8807 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/logger.py @@ -0,0 +1,90 @@ +import logging +import os +import time +from shutil import copytree, ignore_patterns + +import torch +from omegaconf import OmegaConf +from torch.utils.tensorboard import SummaryWriter, summary + + +class LoggerWithTBoard(SummaryWriter): + + def __init__(self, cfg): + # current time stamp and experiment log directory + self.start_time = time.strftime('%y-%m-%dT%H-%M-%S', time.localtime()) + if cfg.exp_name is not None: + self.logdir = os.path.join(cfg.logdir, self.start_time + f'_{cfg.exp_name}') + else: + self.logdir = os.path.join(cfg.logdir, self.start_time) + # init tboard + super().__init__(self.logdir) + # backup the cfg + OmegaConf.save(cfg, os.path.join(self.log_dir, 'cfg.yaml')) + # backup the code state + if cfg.log_code_state: + dest_dir = os.path.join(self.logdir, 'code') + copytree(os.getcwd(), dest_dir, ignore=ignore_patterns(*cfg.patterns_to_ignore)) + + # init logger which handles printing and logging mostly same things to the log file + self.print_logger = logging.getLogger('main') + self.print_logger.setLevel(logging.INFO) + msgfmt = '[%(levelname)s] %(asctime)s - %(name)s \n %(message)s' + datefmt = '%d %b %Y %H:%M:%S' + formatter = logging.Formatter(msgfmt, datefmt) + # stdout + sh = logging.StreamHandler() + sh.setLevel(logging.DEBUG) + sh.setFormatter(formatter) + self.print_logger.addHandler(sh) + # log file + fh = logging.FileHandler(os.path.join(self.log_dir, 'log.txt')) + fh.setLevel(logging.INFO) + fh.setFormatter(formatter) + self.print_logger.addHandler(fh) + + self.print_logger.info(f'Saving logs and checkpoints @ {self.logdir}') + + def log_param_num(self, model): + param_num = sum(p.numel() for p in model.parameters() if p.requires_grad) + self.print_logger.info(f'The number of parameters: {param_num/1e+6:.3f} mil') + self.add_scalar('num_params', param_num, 0) + return param_num + + def log_iter_loss(self, loss, iter, phase): + self.add_scalar(f'{phase}/loss_iter', loss, iter) + + def log_epoch_loss(self, loss, epoch, phase): + self.add_scalar(f'{phase}/loss', loss, epoch) + self.print_logger.info(f'{phase} ({epoch}): loss {loss:.3f};') + + def log_epoch_metrics(self, metrics_dict, epoch, phase): + for metric, val in metrics_dict.items(): + self.add_scalar(f'{phase}/{metric}', val, epoch) + metrics_dict = {k: round(v, 4) for k, v in metrics_dict.items()} + self.print_logger.info(f'{phase} ({epoch}) metrics: {metrics_dict};') + + def log_test_metrics(self, metrics_dict, hparams_dict, best_epoch): + allowed_types = (int, float, str, bool, torch.Tensor) + hparams_dict = {k: v for k, v in hparams_dict.items() if isinstance(v, allowed_types)} + metrics_dict = {f'test/{k}': round(v, 4) for k, v in metrics_dict.items()} + exp, ssi, sei = summary.hparams(hparams_dict, metrics_dict) + self.file_writer.add_summary(exp) + self.file_writer.add_summary(ssi) + self.file_writer.add_summary(sei) + for k, v in metrics_dict.items(): + self.add_scalar(k, v, best_epoch) + self.print_logger.info(f'test ({best_epoch}) metrics: {metrics_dict};') + + def log_best_model(self, model, loss, epoch, optimizer, metrics_dict): + model_name = model.__class__.__name__ + self.best_model_path = os.path.join(self.logdir, f'{model_name}-{self.start_time}.pt') + checkpoint = { + 'loss': loss, + 'metrics': metrics_dict, + 'epoch': epoch, + 'optimizer': optimizer.state_dict(), + 'model': model.state_dict(), + } + torch.save(checkpoint, self.best_model_path) + self.print_logger.info(f'Saved model in {self.best_model_path}') diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/loss.py b/foleycrafter/models/specvqgan/modules/losses/vggishish/loss.py new file mode 100644 index 0000000000000000000000000000000000000000..bae76571909eec571aaf075d58e3dea8f6424546 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/loss.py @@ -0,0 +1,41 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.optim as optim + +class WeightedCrossEntropy(nn.CrossEntropyLoss): + + def __init__(self, weights, **pytorch_ce_loss_args) -> None: + super().__init__(reduction='none', **pytorch_ce_loss_args) + self.weights = weights + + def __call__(self, outputs, targets, to_weight=True): + loss = super().__call__(outputs, targets) + if to_weight: + return (loss * self.weights[targets]).sum() / self.weights[targets].sum() + else: + return loss.mean() + + +if __name__ == '__main__': + x = torch.randn(10, 5) + target = torch.randint(0, 5, (10,)) + weights = torch.tensor([1., 2., 3., 4., 5.]) + + # criterion_weighted = nn.CrossEntropyLoss(weight=weights) + # loss_weighted = criterion_weighted(x, target) + + # criterion_weighted_manual = nn.CrossEntropyLoss(reduction='none') + # loss_weighted_manual = criterion_weighted_manual(x, target) + # print(loss_weighted, loss_weighted_manual.mean()) + # loss_weighted_manual = (loss_weighted_manual * weights[target]).sum() / weights[target].sum() + # print(loss_weighted, loss_weighted_manual) + # print(torch.allclose(loss_weighted, loss_weighted_manual)) + + pytorch_weighted = nn.CrossEntropyLoss(weight=weights) + pytorch_unweighted = nn.CrossEntropyLoss() + custom = WeightedCrossEntropy(weights) + + assert torch.allclose(pytorch_weighted(x, target), custom(x, target, to_weight=True)) + assert torch.allclose(pytorch_unweighted(x, target), custom(x, target, to_weight=False)) + print(custom(x, target, to_weight=True), custom(x, target, to_weight=False)) diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/metrics.py b/foleycrafter/models/specvqgan/modules/losses/vggishish/metrics.py new file mode 100644 index 0000000000000000000000000000000000000000..16905224c665491b9869d7641c1fe17689816a4b --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/metrics.py @@ -0,0 +1,69 @@ +import logging + +import numpy as np +import scipy +import torch +from sklearn.metrics import average_precision_score, roc_auc_score + +logger = logging.getLogger(f'main.{__name__}') + +def metrics(targets, outputs, topk=(1, 5)): + """ + Adapted from https://github.com/hche11/VGGSound/blob/master/utils.py + + Calculate statistics including mAP, AUC, and d-prime. + Args: + output: 2d tensors, (dataset_size, classes_num) - before softmax + target: 1d tensors, (dataset_size, ) + topk: tuple + Returns: + metric_dict: a dict of metrics + """ + metrics_dict = dict() + + num_cls = outputs.shape[-1] + + # accuracy@k + _, preds = torch.topk(outputs, k=max(topk), dim=1) + correct_for_maxtopk = preds == targets.view(-1, 1).expand_as(preds) + for k in topk: + metrics_dict[f'accuracy_{k}'] = float(correct_for_maxtopk[:, :k].sum() / correct_for_maxtopk.shape[0]) + + # avg precision, average roc_auc, and dprime + targets = torch.nn.functional.one_hot(targets, num_classes=num_cls) + + # ids of the predicted classes (same as softmax) + targets_pred = torch.softmax(outputs, dim=1) + + targets = targets.numpy() + targets_pred = targets_pred.numpy() + + # one-vs-rest + avg_p = [average_precision_score(targets[:, c], targets_pred[:, c], average=None) for c in range(num_cls)] + try: + roc_aucs = [roc_auc_score(targets[:, c], targets_pred[:, c], average=None) for c in range(num_cls)] + except ValueError: + logger.warning('Weird... Some classes never occured in targets. Do not trust the metrics.') + roc_aucs = np.array([0.5]) + avg_p = np.array([0]) + + metrics_dict['mAP'] = np.mean(avg_p) + metrics_dict['mROCAUC'] = np.mean(roc_aucs) + # Percent point function (ppf) (inverse of cdf — percentiles). + metrics_dict['dprime'] = scipy.stats.norm().ppf(metrics_dict['mROCAUC']) * np.sqrt(2) + + return metrics_dict + + +if __name__ == '__main__': + targets = torch.tensor([3, 3, 1, 2, 1, 0]) + outputs = torch.tensor([ + [1.2, 1.3, 1.1, 1.5], + [1.3, 1.4, 1.0, 1.1], + [1.5, 1.1, 1.4, 1.3], + [1.0, 1.2, 1.4, 1.5], + [1.2, 1.3, 1.1, 1.1], + [1.2, 1.1, 1.1, 1.1], + ]).float() + metrics_dict = metrics(targets, outputs, topk=(1, 3)) + print(metrics_dict) diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/model.py b/foleycrafter/models/specvqgan/modules/losses/vggishish/model.py new file mode 100644 index 0000000000000000000000000000000000000000..d5069bad0d9311e6e2c082a63eca165f7a908675 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/model.py @@ -0,0 +1,77 @@ +import torch +import torch.nn as nn + + +class VGGishish(nn.Module): + + def __init__(self, conv_layers, use_bn, num_classes): + ''' + Mostly from + https://pytorch.org/vision/0.8/_modules/torchvision/models/vgg.html + ''' + super().__init__() + layers = [] + in_channels = 1 + + # a list of channels with 'MP' (maxpool) from config + for v in conv_layers: + if v == 'MP': + layers += [nn.MaxPool2d(kernel_size=2, stride=2)] + else: + conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1, stride=1) + if use_bn: + layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)] + else: + layers += [conv2d, nn.ReLU(inplace=True)] + in_channels = v + self.features = nn.Sequential(*layers) + + self.avgpool = nn.AdaptiveAvgPool2d((5, 10)) + + self.flatten = nn.Flatten() + self.classifier = nn.Sequential( + nn.Linear(512 * 5 * 10, 4096), + nn.ReLU(True), + nn.Linear(4096, 4096), + nn.ReLU(True), + nn.Linear(4096, num_classes) + ) + + # weight init + self.reset_parameters() + + def forward(self, x): + # adding channel dim for conv2d (B, 1, F, T) <- + x = x.unsqueeze(1) + # backbone (B, 1, 5, 53) <- (B, 1, 80, 860) + x = self.features(x) + # adaptive avg pooling (B, 1, 5, 10) <- (B, 1, 5, 53) – if no MP is used as the end of VGG + x = self.avgpool(x) + # flatten + x = self.flatten(x) + # classify + x = self.classifier(x) + return x + + def reset_parameters(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') + if m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.constant_(m.bias, 0) + + +if __name__ == '__main__': + num_classes = 309 + inputs = torch.rand(3, 80, 848) + conv_layers = [64, 64, 'MP', 128, 128, 'MP', 256, 256, 256, 'MP', 512, 512, 512, 'MP', 512, 512, 512] + # conv_layers = [64, 'MP', 128, 'MP', 256, 256, 'MP', 512, 512, 'MP'] + model = VGGishish(conv_layers, use_bn=False, num_classes=num_classes) + outputs = model(inputs) + print(outputs.shape) diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/predict.py b/foleycrafter/models/specvqgan/modules/losses/vggishish/predict.py new file mode 100644 index 0000000000000000000000000000000000000000..e9d13f30153cd43a4a8bcfe2da4b9a53846bf1eb --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/predict.py @@ -0,0 +1,90 @@ +import os +from torch.utils.data import DataLoader +import torchvision +from tqdm import tqdm +from dataset import VGGSound +import torch +import torch.nn as nn +from metrics import metrics +from omegaconf import OmegaConf +from model import VGGishish +from transforms import Crop, StandardNormalizeAudio, ToTensor + + +if __name__ == '__main__': + cfg_cli = OmegaConf.from_cli() + print(cfg_cli.config) + cfg_yml = OmegaConf.load(cfg_cli.config) + # the latter arguments are prioritized + cfg = OmegaConf.merge(cfg_yml, cfg_cli) + OmegaConf.set_readonly(cfg, True) + print(OmegaConf.to_yaml(cfg)) + + # logger = LoggerWithTBoard(cfg) + transforms = [ + StandardNormalizeAudio(cfg.mels_path), + ToTensor(), + ] + if cfg.cropped_size not in [None, 'None', 'none']: + transforms.append(Crop(cfg.cropped_size)) + transforms = torchvision.transforms.transforms.Compose(transforms) + + datasets = { + 'test': VGGSound('test', cfg.mels_path, transforms), + } + + loaders = { + 'test': DataLoader(datasets['test'], batch_size=cfg.batch_size, + num_workers=cfg.num_workers, pin_memory=True) + } + + device = torch.device(cfg.device if torch.cuda.is_available() else 'cpu') + model = VGGishish(cfg.conv_layers, cfg.use_bn, num_classes=len(datasets['test'].target2label)) + model = model.to(device) + + optimizer = torch.optim.Adam(model.parameters(), lr=cfg.learning_rate) + criterion = nn.CrossEntropyLoss() + + # loading the best model + folder_name = os.path.split(cfg.config)[0].split('/')[-1] + print(folder_name) + ckpt = torch.load(f'./logs/{folder_name}/vggishish-{folder_name}.pt', map_location='cpu') + model.load_state_dict(ckpt['model']) + print((f'The model was trained for {ckpt["epoch"]} epochs. Loss: {ckpt["loss"]:.4f}')) + + # Testing the model + model.eval() + running_loss = 0 + preds_from_each_batch = [] + targets_from_each_batch = [] + + for i, batch in enumerate(tqdm(loaders['test'])): + inputs = batch['input'].to(device) + targets = batch['target'].to(device) + + # zero the parameter gradients + optimizer.zero_grad() + + # forward + backward + optimize + with torch.set_grad_enabled(False): + outputs = model(inputs) + loss = criterion(outputs, targets) + + # loss + running_loss += loss.item() + + # for metrics calculation later on + preds_from_each_batch += [outputs.detach().cpu()] + targets_from_each_batch += [targets.cpu()] + + # logging metrics + preds_from_each_batch = torch.cat(preds_from_each_batch) + targets_from_each_batch = torch.cat(targets_from_each_batch) + test_metrics_dict = metrics(targets_from_each_batch, preds_from_each_batch) + test_metrics_dict['avg_loss'] = running_loss / len(loaders['test']) + test_metrics_dict['param_num'] = sum(p.numel() for p in model.parameters() if p.requires_grad) + + # TODO: I have no idea why tboard doesn't keep metrics (hparams) in a tensorboard when + # I run this experiment from cli: `python main.py config=./configs/vggish.yaml` + # while when I run it in vscode debugger the metrics are present in the tboard (weird) + print(test_metrics_dict) diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/predict_gh.py b/foleycrafter/models/specvqgan/modules/losses/vggishish/predict_gh.py new file mode 100644 index 0000000000000000000000000000000000000000..c912d2f506febc0f67f1a7e7844d250f4743b6d8 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/predict_gh.py @@ -0,0 +1,66 @@ +import os +import sys +import json +from torch.utils.data import DataLoader +import torchvision +from tqdm import tqdm +from dataset import GreatestHit, AMT_test +import torch +import torch.nn as nn +from metrics import metrics +from omegaconf import OmegaConf +from model import VGGishish +from transforms import Crop, StandardNormalizeAudio, ToTensor + + +if __name__ == '__main__': + cfg_cli = sys.argv[1] + target_path = sys.argv[2] + model_path = sys.argv[3] + cfg_yml = OmegaConf.load(cfg_cli) + # the latter arguments are prioritized + cfg = cfg_yml + OmegaConf.set_readonly(cfg, True) + # print(OmegaConf.to_yaml(cfg)) + + device = torch.device(cfg.device if torch.cuda.is_available() else 'cpu') + transforms = [ + StandardNormalizeAudio(cfg.mels_path), + ] + if cfg.cropped_size not in [None, 'None', 'none']: + transforms.append(Crop(cfg.cropped_size)) + transforms.append(ToTensor()) + transforms = torchvision.transforms.transforms.Compose(transforms) + + testset = AMT_test(target_path, transforms, action_only=cfg.action_only, material_only=cfg.material_only) + loader = DataLoader(testset, batch_size=cfg.batch_size, + num_workers=cfg.num_workers, pin_memory=True) + + model = VGGishish(cfg.conv_layers, cfg.use_bn, num_classes=len(testset.label2target)) + ckpt = torch.load(model_path)['model'] + model.load_state_dict(ckpt, strict=True) + model = model.to(device) + + model.eval() + + if cfg.cls_weights_in_loss: + weights = 1 / testset.class_counts + else: + weights = torch.ones(len(testset.label2target)) + + preds_from_each_batch = [] + file_path_from_each_batch = [] + for batch in tqdm(loader): + inputs = batch['input'].to(device) + file_path = batch['file_path'] + with torch.set_grad_enabled(False): + outputs = model(inputs) + # for metrics calculation later on + preds_from_each_batch += [outputs.detach().cpu()] + file_path_from_each_batch += file_path + preds_from_each_batch = torch.cat(preds_from_each_batch) + _, preds = torch.topk(preds_from_each_batch, k=1) + pred_dict = {fp: int(p.item()) for fp, p in zip(file_path_from_each_batch, preds)} + mel_parent_dir = os.path.dirname(list(pred_dict.keys())[0]) + pred_list = [pred_dict[os.path.join(mel_parent_dir, f'{i}.npy')] for i in range(len(pred_dict))] + json.dump(pred_list, open(target_path + f'_{cfg.exp_name}_preds.json', 'w')) diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/train_melception.py b/foleycrafter/models/specvqgan/modules/losses/vggishish/train_melception.py new file mode 100644 index 0000000000000000000000000000000000000000..8adc5aa6e0e32a66cdbb7b449483a3b23d9b0ef9 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/train_melception.py @@ -0,0 +1,241 @@ +import random + +import numpy as np +import torch +import torchvision +from omegaconf import OmegaConf +from torch.utils.data.dataloader import DataLoader +from torchvision.models.inception import BasicConv2d, Inception3 +from tqdm import tqdm + +from dataset import VGGSound +from logger import LoggerWithTBoard +from loss import WeightedCrossEntropy +from metrics import metrics +from transforms import Crop, StandardNormalizeAudio, ToTensor + + +# TODO: refactor ./evaluation/feature_extractors/melception.py to handle this class as well. +# So far couldn't do it because of the difference in outputs +class Melception(Inception3): + + def __init__(self, num_classes, **kwargs): + # inception = Melception(num_classes=309) + super().__init__(num_classes=num_classes, **kwargs) + # the same as https://github.com/pytorch/vision/blob/5339e63148/torchvision/models/inception.py#L95 + # but for 1-channel input instead of RGB. + self.Conv2d_1a_3x3 = BasicConv2d(1, 32, kernel_size=3, stride=2) + # also the 'hight' of the mel spec is 80 (vs 299 in RGB) we remove all max pool from Inception + self.maxpool1 = torch.nn.Identity() + self.maxpool2 = torch.nn.Identity() + + def forward(self, x): + x = x.unsqueeze(1) + return super().forward(x) + +def train_inception_scorer(cfg): + logger = LoggerWithTBoard(cfg) + + random.seed(cfg.seed) + np.random.seed(cfg.seed) + torch.manual_seed(cfg.seed) + torch.cuda.manual_seed_all(cfg.seed) + # makes iterations faster (in this case 30%) if your inputs are of a fixed size + # https://discuss.pytorch.org/t/what-does-torch-backends-cudnn-benchmark-do/5936/3 + torch.backends.cudnn.benchmark = True + + meta_path = './data/vggsound.csv' + train_ids_path = './data/vggsound_train.txt' + cache_path = './data/' + splits_path = cache_path + + transforms = [ + StandardNormalizeAudio(cfg.mels_path, train_ids_path, cache_path), + ] + if cfg.cropped_size not in [None, 'None', 'none']: + logger.print_logger.info(f'Using cropping {cfg.cropped_size}') + transforms.append(Crop(cfg.cropped_size)) + transforms.append(ToTensor()) + transforms = torchvision.transforms.transforms.Compose(transforms) + + datasets = { + 'train': VGGSound('train', cfg.mels_path, transforms, splits_path, meta_path), + 'valid': VGGSound('valid', cfg.mels_path, transforms, splits_path, meta_path), + 'test': VGGSound('test', cfg.mels_path, transforms, splits_path, meta_path), + } + + loaders = { + 'train': DataLoader(datasets['train'], batch_size=cfg.batch_size, shuffle=True, drop_last=True, + num_workers=cfg.num_workers, pin_memory=True), + 'valid': DataLoader(datasets['valid'], batch_size=cfg.batch_size, + num_workers=cfg.num_workers, pin_memory=True), + 'test': DataLoader(datasets['test'], batch_size=cfg.batch_size, + num_workers=cfg.num_workers, pin_memory=True), + } + + device = torch.device(cfg.device if torch.cuda.is_available() else 'cpu') + + model = Melception(num_classes=len(datasets['train'].target2label)) + model = model.to(device) + param_num = logger.log_param_num(model) + + if cfg.optimizer == 'adam': + optimizer = torch.optim.Adam( + model.parameters(), lr=cfg.learning_rate, betas=cfg.betas, weight_decay=cfg.weight_decay) + elif cfg.optimizer == 'sgd': + optimizer = torch.optim.SGD( + model.parameters(), lr=cfg.learning_rate, momentum=cfg.momentum, weight_decay=cfg.weight_decay) + else: + raise NotImplementedError + + if cfg.cls_weights_in_loss: + weights = 1 / datasets['train'].class_counts + else: + weights = torch.ones(len(datasets['train'].target2label)) + criterion = WeightedCrossEntropy(weights.to(device)) + + # loop over the train and validation multiple times (typical PT boilerplate) + no_change_epochs = 0 + best_valid_loss = float('inf') + early_stop_triggered = False + + for epoch in range(cfg.num_epochs): + + for phase in ['train', 'valid']: + if phase == 'train': + model.train() + else: + model.eval() + + running_loss = 0 + preds_from_each_batch = [] + targets_from_each_batch = [] + + prog_bar = tqdm(loaders[phase], f'{phase} ({epoch})', ncols=0) + for i, batch in enumerate(prog_bar): + inputs = batch['input'].to(device) + targets = batch['target'].to(device) + + # zero the parameter gradients + optimizer.zero_grad() + + # forward + backward + optimize + with torch.set_grad_enabled(phase == 'train'): + # inception v3 + if phase == 'train': + outputs, aux_outputs = model(inputs) + loss1 = criterion(outputs, targets) + loss2 = criterion(aux_outputs, targets) + loss = loss1 + 0.4*loss2 + loss = criterion(outputs, targets, to_weight=True) + else: + outputs = model(inputs) + loss = criterion(outputs, targets, to_weight=False) + + if phase == 'train': + loss.backward() + optimizer.step() + + # loss + running_loss += loss.item() + + # for metrics calculation later on + preds_from_each_batch += [outputs.detach().cpu()] + targets_from_each_batch += [targets.cpu()] + + # iter logging + if i % 50 == 0: + logger.log_iter_loss(loss.item(), epoch*len(loaders[phase])+i, phase) + # tracks loss in the tqdm progress bar + prog_bar.set_postfix(loss=loss.item()) + + # logging loss + epoch_loss = running_loss / len(loaders[phase]) + logger.log_epoch_loss(epoch_loss, epoch, phase) + + # logging metrics + preds_from_each_batch = torch.cat(preds_from_each_batch) + targets_from_each_batch = torch.cat(targets_from_each_batch) + metrics_dict = metrics(targets_from_each_batch, preds_from_each_batch) + logger.log_epoch_metrics(metrics_dict, epoch, phase) + + # Early stopping + if phase == 'valid': + if epoch_loss < best_valid_loss: + no_change_epochs = 0 + best_valid_loss = epoch_loss + logger.log_best_model(model, epoch_loss, epoch, optimizer, metrics_dict) + else: + no_change_epochs += 1 + logger.print_logger.info( + f'Valid loss hasnt changed for {no_change_epochs} patience: {cfg.patience}' + ) + if no_change_epochs >= cfg.patience: + early_stop_triggered = True + + if early_stop_triggered: + logger.print_logger.info(f'Training is early stopped @ {epoch}') + break + + logger.print_logger.info('Finished Training') + + # loading the best model + ckpt = torch.load(logger.best_model_path) + model.load_state_dict(ckpt['model']) + logger.print_logger.info(f'Loading the best model from {logger.best_model_path}') + logger.print_logger.info((f'The model was trained for {ckpt["epoch"]} epochs. Loss: {ckpt["loss"]:.4f}')) + + # Testing the model + model.eval() + running_loss = 0 + preds_from_each_batch = [] + targets_from_each_batch = [] + + for i, batch in enumerate(loaders['test']): + inputs = batch['input'].to(device) + targets = batch['target'].to(device) + + # zero the parameter gradients + optimizer.zero_grad() + + # forward + backward + optimize + with torch.set_grad_enabled(False): + outputs = model(inputs) + loss = criterion(outputs, targets, to_weight=False) + + # loss + running_loss += loss.item() + + # for metrics calculation later on + preds_from_each_batch += [outputs.detach().cpu()] + targets_from_each_batch += [targets.cpu()] + + # logging metrics + preds_from_each_batch = torch.cat(preds_from_each_batch) + targets_from_each_batch = torch.cat(targets_from_each_batch) + test_metrics_dict = metrics(targets_from_each_batch, preds_from_each_batch) + test_metrics_dict['avg_loss'] = running_loss / len(loaders['test']) + test_metrics_dict['param_num'] = param_num + # TODO: I have no idea why tboard doesn't keep metrics (hparams) when + # I run this experiment from cli: `python train_melception.py config=./configs/vggish.yaml` + # while when I run it in vscode debugger the metrics are logger (wtf) + logger.log_test_metrics(test_metrics_dict, dict(cfg), ckpt['epoch']) + + logger.print_logger.info('Finished the experiment') + + +if __name__ == '__main__': + # input = torch.rand(16, 1, 80, 848) + # output, aux = inception(input) + # print(output.shape, aux.shape) + # Expected input size: (3, 299, 299) in RGB -> (1, 80, 848) in Mel Spec + # train_inception_scorer() + + cfg_cli = OmegaConf.from_cli() + cfg_yml = OmegaConf.load(cfg_cli.config) + # the latter arguments are prioritized + cfg = OmegaConf.merge(cfg_yml, cfg_cli) + OmegaConf.set_readonly(cfg, True) + print(OmegaConf.to_yaml(cfg)) + + train_inception_scorer(cfg) diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/train_vggishish.py b/foleycrafter/models/specvqgan/modules/losses/vggishish/train_vggishish.py new file mode 100644 index 0000000000000000000000000000000000000000..205668224ec87a9ce571f6428531080231b1c16b --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/train_vggishish.py @@ -0,0 +1,199 @@ +from loss import WeightedCrossEntropy +import random + +import numpy as np +import torch +import torchvision +from omegaconf import OmegaConf +from torch.utils.data.dataloader import DataLoader +from tqdm import tqdm + +from dataset import VGGSound +from transforms import Crop, StandardNormalizeAudio, ToTensor +from logger import LoggerWithTBoard +from metrics import metrics +from model import VGGishish + +if __name__ == "__main__": + cfg_cli = OmegaConf.from_cli() + cfg_yml = OmegaConf.load(cfg_cli.config) + # the latter arguments are prioritized + cfg = OmegaConf.merge(cfg_yml, cfg_cli) + OmegaConf.set_readonly(cfg, True) + print(OmegaConf.to_yaml(cfg)) + + logger = LoggerWithTBoard(cfg) + + random.seed(cfg.seed) + np.random.seed(cfg.seed) + torch.manual_seed(cfg.seed) + torch.cuda.manual_seed_all(cfg.seed) + # makes iterations faster (in this case 30%) if your inputs are of a fixed size + # https://discuss.pytorch.org/t/what-does-torch-backends-cudnn-benchmark-do/5936/3 + torch.backends.cudnn.benchmark = True + + transforms = [ + StandardNormalizeAudio(cfg.mels_path), + ] + if cfg.cropped_size not in [None, 'None', 'none']: + logger.print_logger.info(f'Using cropping {cfg.cropped_size}') + transforms.append(Crop(cfg.cropped_size)) + transforms.append(ToTensor()) + transforms = torchvision.transforms.transforms.Compose(transforms) + + datasets = { + 'train': VGGSound('train', cfg.mels_path, transforms), + 'valid': VGGSound('valid', cfg.mels_path, transforms), + 'test': VGGSound('test', cfg.mels_path, transforms), + } + + loaders = { + 'train': DataLoader(datasets['train'], batch_size=cfg.batch_size, shuffle=True, drop_last=True, + num_workers=cfg.num_workers, pin_memory=True), + 'valid': DataLoader(datasets['valid'], batch_size=cfg.batch_size, + num_workers=cfg.num_workers, pin_memory=True), + 'test': DataLoader(datasets['test'], batch_size=cfg.batch_size, + num_workers=cfg.num_workers, pin_memory=True), + } + + device = torch.device(cfg.device if torch.cuda.is_available() else 'cpu') + + model = VGGishish(cfg.conv_layers, cfg.use_bn, num_classes=len(datasets['train'].target2label)) + model = model.to(device) + param_num = logger.log_param_num(model) + + if cfg.optimizer == 'adam': + optimizer = torch.optim.Adam( + model.parameters(), lr=cfg.learning_rate, betas=cfg.betas, weight_decay=cfg.weight_decay) + elif cfg.optimizer == 'sgd': + optimizer = torch.optim.SGD( + model.parameters(), lr=cfg.learning_rate, momentum=cfg.momentum, weight_decay=cfg.weight_decay) + else: + raise NotImplementedError + + if cfg.cls_weights_in_loss: + weights = 1 / datasets['train'].class_counts + else: + weights = torch.ones(len(datasets['train'].target2label)) + criterion = WeightedCrossEntropy(weights.to(device)) + + # loop over the train and validation multiple times (typical PT boilerplate) + no_change_epochs = 0 + best_valid_loss = float('inf') + early_stop_triggered = False + + for epoch in range(cfg.num_epochs): + + for phase in ['train', 'valid']: + if phase == 'train': + model.train() + else: + model.eval() + + running_loss = 0 + preds_from_each_batch = [] + targets_from_each_batch = [] + + prog_bar = tqdm(loaders[phase], f'{phase} ({epoch})', ncols=0) + for i, batch in enumerate(prog_bar): + inputs = batch['input'].to(device) + targets = batch['target'].to(device) + + # zero the parameter gradients + optimizer.zero_grad() + + # forward + backward + optimize + with torch.set_grad_enabled(phase == 'train'): + outputs = model(inputs) + loss = criterion(outputs, targets, to_weight=phase == 'train') + + if phase == 'train': + loss.backward() + optimizer.step() + + # loss + running_loss += loss.item() + + # for metrics calculation later on + preds_from_each_batch += [outputs.detach().cpu()] + targets_from_each_batch += [targets.cpu()] + + # iter logging + if i % 50 == 0: + logger.log_iter_loss(loss.item(), epoch*len(loaders[phase])+i, phase) + # tracks loss in the tqdm progress bar + prog_bar.set_postfix(loss=loss.item()) + + # logging loss + epoch_loss = running_loss / len(loaders[phase]) + logger.log_epoch_loss(epoch_loss, epoch, phase) + + # logging metrics + preds_from_each_batch = torch.cat(preds_from_each_batch) + targets_from_each_batch = torch.cat(targets_from_each_batch) + metrics_dict = metrics(targets_from_each_batch, preds_from_each_batch) + logger.log_epoch_metrics(metrics_dict, epoch, phase) + + # Early stopping + if phase == 'valid': + if epoch_loss < best_valid_loss: + no_change_epochs = 0 + best_valid_loss = epoch_loss + logger.log_best_model(model, epoch_loss, epoch, optimizer, metrics_dict) + else: + no_change_epochs += 1 + logger.print_logger.info( + f'Valid loss hasnt changed for {no_change_epochs} patience: {cfg.patience}' + ) + if no_change_epochs >= cfg.patience: + early_stop_triggered = True + + if early_stop_triggered: + logger.print_logger.info(f'Training is early stopped @ {epoch}') + break + + logger.print_logger.info('Finished Training') + + # loading the best model + ckpt = torch.load(logger.best_model_path) + model.load_state_dict(ckpt['model']) + logger.print_logger.info(f'Loading the best model from {logger.best_model_path}') + logger.print_logger.info((f'The model was trained for {ckpt["epoch"]} epochs. Loss: {ckpt["loss"]:.4f}')) + + # Testing the model + model.eval() + running_loss = 0 + preds_from_each_batch = [] + targets_from_each_batch = [] + + for i, batch in enumerate(loaders['test']): + inputs = batch['input'].to(device) + targets = batch['target'].to(device) + + # zero the parameter gradients + optimizer.zero_grad() + + # forward + backward + optimize + with torch.set_grad_enabled(False): + outputs = model(inputs) + loss = criterion(outputs, targets, to_weight=False) + + # loss + running_loss += loss.item() + + # for metrics calculation later on + preds_from_each_batch += [outputs.detach().cpu()] + targets_from_each_batch += [targets.cpu()] + + # logging metrics + preds_from_each_batch = torch.cat(preds_from_each_batch) + targets_from_each_batch = torch.cat(targets_from_each_batch) + test_metrics_dict = metrics(targets_from_each_batch, preds_from_each_batch) + test_metrics_dict['avg_loss'] = running_loss / len(loaders['test']) + test_metrics_dict['param_num'] = param_num + # TODO: I have no idea why tboard doesn't keep metrics (hparams) when + # I run this experiment from cli: `python train_vggishish.py config=./configs/vggish.yaml` + # while when I run it in vscode debugger the metrics are logger (wtf) + logger.log_test_metrics(test_metrics_dict, dict(cfg), ckpt['epoch']) + + logger.print_logger.info('Finished the experiment') diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/train_vggishish_gh.py b/foleycrafter/models/specvqgan/modules/losses/vggishish/train_vggishish_gh.py new file mode 100644 index 0000000000000000000000000000000000000000..7b879131f3f32589c09eb07e818157da21797bb7 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/train_vggishish_gh.py @@ -0,0 +1,218 @@ +from loss import WeightedCrossEntropy +import random +import os +import sys +import json + +import numpy as np +import torch +import torchvision +from omegaconf import OmegaConf +from torch.utils.data.dataloader import DataLoader +from tqdm import tqdm + +from dataset import GreatestHit, AMT_test +from transforms import Crop, StandardNormalizeAudio, ToTensor +from logger import LoggerWithTBoard +from metrics import metrics +from model import VGGishish + + +if __name__ == "__main__": + cfg_cli = sys.argv[1] + cfg_yml = OmegaConf.load(cfg_cli) + # the latter arguments are prioritized + cfg = cfg_yml + OmegaConf.set_readonly(cfg, True) + print(OmegaConf.to_yaml(cfg)) + + logger = LoggerWithTBoard(cfg) + + random.seed(cfg.seed) + np.random.seed(cfg.seed) + torch.manual_seed(cfg.seed) + torch.cuda.manual_seed_all(cfg.seed) + # makes iterations faster (in this case 30%) if your inputs are of a fixed size + # https://discuss.pytorch.org/t/what-does-torch-backends-cudnn-benchmark-do/5936/3 + torch.backends.cudnn.benchmark = True + + transforms = [ + StandardNormalizeAudio(cfg.mels_path), + ] + if cfg.cropped_size not in [None, 'None', 'none']: + logger.print_logger.info(f'Using cropping {cfg.cropped_size}') + transforms.append(Crop(cfg.cropped_size)) + transforms.append(ToTensor()) + transforms = torchvision.transforms.transforms.Compose(transforms) + + datasets = { + 'train': GreatestHit('train', cfg.mels_path, transforms, action_only=cfg.action_only, material_only=cfg.material_only), + 'valid': GreatestHit('valid', cfg.mels_path, transforms, action_only=cfg.action_only, material_only=cfg.material_only), + 'test': GreatestHit('test', cfg.mels_path, transforms, action_only=cfg.action_only, material_only=cfg.material_only), + } + + loaders = { + 'train': DataLoader(datasets['train'], batch_size=cfg.batch_size, shuffle=True, drop_last=True, + num_workers=cfg.num_workers, pin_memory=True), + 'valid': DataLoader(datasets['valid'], batch_size=cfg.batch_size, + num_workers=cfg.num_workers, pin_memory=True), + 'test': DataLoader(datasets['test'], batch_size=cfg.batch_size, + num_workers=cfg.num_workers, pin_memory=True), + } + + device = torch.device(cfg.device if torch.cuda.is_available() else 'cpu') + + model = VGGishish(cfg.conv_layers, cfg.use_bn, num_classes=len(datasets['train'].label2target)) + model = model.to(device) + if cfg.load_model is not None: + state_dict = torch.load(cfg.load_model, map_location=device)['model'] + target_dict = {} + # ignore the last layer + for key, v in state_dict.items(): + # ignore classifier + if 'classifier' not in key: + target_dict[key] = v + model.load_state_dict(target_dict, strict=False) + param_num = logger.log_param_num(model) + + if cfg.optimizer == 'adam': + optimizer = torch.optim.Adam( + model.parameters(), lr=cfg.learning_rate, betas=cfg.betas, weight_decay=cfg.weight_decay) + elif cfg.optimizer == 'sgd': + optimizer = torch.optim.SGD( + model.parameters(), lr=cfg.learning_rate, momentum=cfg.momentum, weight_decay=cfg.weight_decay) + else: + raise NotImplementedError + + if cfg.cls_weights_in_loss: + weights = 1 / datasets['train'].class_counts + else: + weights = torch.ones(len(datasets['train'].label2target)) + criterion = WeightedCrossEntropy(weights.to(device)) + + # loop over the train and validation multiple times (typical PT boilerplate) + no_change_epochs = 0 + best_valid_loss = float('inf') + early_stop_triggered = False + + for epoch in range(cfg.num_epochs): + + for phase in ['train', 'valid']: + if phase == 'train': + model.train() + else: + model.eval() + + running_loss = 0 + preds_from_each_batch = [] + targets_from_each_batch = [] + + prog_bar = tqdm(loaders[phase], f'{phase} ({epoch})', ncols=0) + for i, batch in enumerate(prog_bar): + inputs = batch['input'].to(device) + targets = batch['target'].to(device) + + # zero the parameter gradients + optimizer.zero_grad() + + # forward + backward + optimize + with torch.set_grad_enabled(phase == 'train'): + outputs = model(inputs) + loss = criterion(outputs, targets, to_weight=phase == 'train') + + if phase == 'train': + loss.backward() + optimizer.step() + + # loss + running_loss += loss.item() + + # for metrics calculation later on + preds_from_each_batch += [outputs.detach().cpu()] + targets_from_each_batch += [targets.cpu()] + + # iter logging + if i % 50 == 0: + logger.log_iter_loss(loss.item(), epoch*len(loaders[phase])+i, phase) + # tracks loss in the tqdm progress bar + prog_bar.set_postfix(loss=loss.item()) + + # logging loss + epoch_loss = running_loss / len(loaders[phase]) + logger.log_epoch_loss(epoch_loss, epoch, phase) + + # logging metrics + preds_from_each_batch = torch.cat(preds_from_each_batch) + targets_from_each_batch = torch.cat(targets_from_each_batch) + if cfg.action_only: + metrics_dict = metrics(targets_from_each_batch, preds_from_each_batch, topk=(1,)) + else: + metrics_dict = metrics(targets_from_each_batch, preds_from_each_batch, topk=(1, 5)) + logger.log_epoch_metrics(metrics_dict, epoch, phase) + + # Early stopping + if phase == 'valid': + if epoch_loss < best_valid_loss: + no_change_epochs = 0 + best_valid_loss = epoch_loss + logger.log_best_model(model, epoch_loss, epoch, optimizer, metrics_dict) + else: + no_change_epochs += 1 + logger.print_logger.info( + f'Valid loss hasnt changed for {no_change_epochs} patience: {cfg.patience}' + ) + if no_change_epochs >= cfg.patience: + early_stop_triggered = True + + if early_stop_triggered: + logger.print_logger.info(f'Training is early stopped @ {epoch}') + break + + logger.print_logger.info('Finished Training') + + # loading the best model + ckpt = torch.load(logger.best_model_path) + model.load_state_dict(ckpt['model']) + logger.print_logger.info(f'Loading the best model from {logger.best_model_path}') + logger.print_logger.info((f'The model was trained for {ckpt["epoch"]} epochs. Loss: {ckpt["loss"]:.4f}')) + + # Testing the model + model.eval() + running_loss = 0 + preds_from_each_batch = [] + targets_from_each_batch = [] + + for i, batch in enumerate(loaders['test']): + inputs = batch['input'].to(device) + targets = batch['target'].to(device) + + # zero the parameter gradients + optimizer.zero_grad() + + # forward + backward + optimize + with torch.set_grad_enabled(False): + outputs = model(inputs) + loss = criterion(outputs, targets, to_weight=False) + + # loss + running_loss += loss.item() + + # for metrics calculation later on + preds_from_each_batch += [outputs.detach().cpu()] + targets_from_each_batch += [targets.cpu()] + + # logging metrics + preds_from_each_batch = torch.cat(preds_from_each_batch) + targets_from_each_batch = torch.cat(targets_from_each_batch) + if cfg.action_only: + test_metrics_dict = metrics(targets_from_each_batch, preds_from_each_batch, topk=(1,)) + else: + test_metrics_dict = metrics(targets_from_each_batch, preds_from_each_batch, topk=(1, 5)) + test_metrics_dict['avg_loss'] = running_loss / len(loaders['test']) + test_metrics_dict['param_num'] = param_num + # TODO: I have no idea why tboard doesn't keep metrics (hparams) when + # I run this experiment from cli: `python train_vggishish.py config=./configs/vggish.yaml` + # while when I run it in vscode debugger the metrics are logger (wtf) + logger.log_test_metrics(test_metrics_dict, dict(cfg), ckpt['epoch']) + + logger.print_logger.info('Finished the experiment') diff --git a/foleycrafter/models/specvqgan/modules/losses/vggishish/transforms.py b/foleycrafter/models/specvqgan/modules/losses/vggishish/transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..551c4d95534a4c6f83484afcf06e1017baafc135 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vggishish/transforms.py @@ -0,0 +1,98 @@ +import logging +import os +from pathlib import Path + +import albumentations +import numpy as np +import torch +from tqdm import tqdm + +logger = logging.getLogger(f'main.{__name__}') + + +class StandardNormalizeAudio(object): + ''' + Frequency-wise normalization + ''' + def __init__(self, specs_dir, train_ids_path='./data/vggsound_train.txt', cache_path='./data/'): + self.specs_dir = specs_dir + self.train_ids_path = train_ids_path + # making the stats filename to match the specs dir name + self.cache_path = os.path.join(cache_path, f'train_means_stds_{Path(specs_dir).stem}.txt') + logger.info('Assuming that the input stats are calculated using preprocessed spectrograms (log)') + self.train_stats = self.calculate_or_load_stats() + + def __call__(self, item): + # just to generalizat the input handling. Useful for FID, IS eval and training other staff + if isinstance(item, dict): + if 'input' in item: + input_key = 'input' + elif 'image' in item: + input_key = 'image' + else: + raise NotImplementedError + item[input_key] = (item[input_key] - self.train_stats['means']) / self.train_stats['stds'] + elif isinstance(item, torch.Tensor): + # broadcasts np.ndarray (80, 1) to (1, 80, 1) because item is torch.Tensor (B, 80, T) + item = (item - self.train_stats['means']) / self.train_stats['stds'] + else: + raise NotImplementedError + return item + + def calculate_or_load_stats(self): + try: + # (F, 2) + train_stats = np.loadtxt(self.cache_path) + means, stds = train_stats.T + logger.info('Trying to load train stats for Standard Normalization of inputs') + except OSError: + logger.info('Could not find the precalculated stats for Standard Normalization. Calculating...') + train_vid_ids = open(self.train_ids_path) + specs_paths = [os.path.join(self.specs_dir, f'{i.rstrip()}_mel.npy') for i in train_vid_ids] + means = [None] * len(specs_paths) + stds = [None] * len(specs_paths) + for i, path in enumerate(tqdm(specs_paths)): + spec = np.load(path) + means[i] = spec.mean(axis=1) + stds[i] = spec.std(axis=1) + # (F) <- (num_files, F) + means = np.array(means).mean(axis=0) + stds = np.array(stds).mean(axis=0) + # saving in two columns + np.savetxt(self.cache_path, np.vstack([means, stds]).T, fmt='%0.8f') + means = means.reshape(-1, 1) + stds = stds.reshape(-1, 1) + return {'means': means, 'stds': stds} + +class ToTensor(object): + + def __call__(self, item): + item['input'] = torch.from_numpy(item['input']).float() + if 'target' in item: + item['target'] = torch.tensor(item['target']) + return item + +class Crop(object): + + def __init__(self, cropped_shape=None, random_crop=False): + self.cropped_shape = cropped_shape + if cropped_shape is not None: + mel_num, spec_len = cropped_shape + if random_crop: + self.cropper = albumentations.RandomCrop + else: + self.cropper = albumentations.CenterCrop + self.preprocessor = albumentations.Compose([self.cropper(mel_num, spec_len)]) + else: + self.preprocessor = lambda **kwargs: kwargs + + def __call__(self, item): + item['input'] = self.preprocessor(image=item['input'])['image'] + return item + + +if __name__ == '__main__': + cropper = Crop([80, 848]) + item = {'input': torch.rand([80, 860])} + outputs = cropper(item) + print(outputs['input'].shape) diff --git a/foleycrafter/models/specvqgan/modules/losses/vqperceptual.py b/foleycrafter/models/specvqgan/modules/losses/vqperceptual.py new file mode 100644 index 0000000000000000000000000000000000000000..80e8d4b445a9c4c3b6513c088c875153e9553151 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/losses/vqperceptual.py @@ -0,0 +1,209 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +import sys + +sys.path.insert(0, '.') # nopep8 +from foleycrafter.models.specvqgan.modules.discriminator.model import (NLayerDiscriminator, NLayerDiscriminator1dFeats, + NLayerDiscriminator1dSpecs, + weights_init) +from foleycrafter.models.specvqgan.modules.losses.lpaps import LPAPS + + +class DummyLoss(nn.Module): + def __init__(self): + super().__init__() + + +def adopt_weight(weight, global_step, threshold=0, value=0.): + if global_step < threshold: + weight = value + return weight + + +def hinge_d_loss(logits_real, logits_fake): + loss_real = torch.mean(F.relu(1. - logits_real)) + loss_fake = torch.mean(F.relu(1. + logits_fake)) + d_loss = 0.5 * (loss_real + loss_fake) + return d_loss + + +def vanilla_d_loss(logits_real, logits_fake): + d_loss = 0.5 * ( + torch.mean(torch.nn.functional.softplus(-logits_real)) + + torch.mean(torch.nn.functional.softplus(logits_fake))) + return d_loss + + +class VQLPAPSWithDiscriminator(nn.Module): + def __init__(self, disc_start, codebook_weight=1.0, pixelloss_weight=1.0, + disc_num_layers=3, disc_in_channels=3, disc_factor=1.0, disc_weight=1.0, + perceptual_weight=1.0, use_actnorm=False, disc_conditional=False, + disc_ndf=64, disc_loss="hinge", min_adapt_weight=0.0, max_adapt_weight=1e4): + super().__init__() + assert disc_loss in ["hinge", "vanilla"] + self.codebook_weight = codebook_weight + self.pixel_weight = pixelloss_weight + self.perceptual_loss = LPAPS().eval() + self.perceptual_weight = perceptual_weight + + self.discriminator = NLayerDiscriminator(input_nc=disc_in_channels, + n_layers=disc_num_layers, + use_actnorm=use_actnorm, + ndf=disc_ndf + ).apply(weights_init) + self.discriminator_iter_start = disc_start + if disc_loss == "hinge": + self.disc_loss = hinge_d_loss + elif disc_loss == "vanilla": + self.disc_loss = vanilla_d_loss + else: + raise ValueError(f"Unknown GAN loss '{disc_loss}'.") + print(f"VQLPAPSWithDiscriminator running with {disc_loss} loss.") + self.disc_factor = disc_factor + self.discriminator_weight = disc_weight + self.disc_conditional = disc_conditional + self.min_adapt_weight = min_adapt_weight + self.max_adapt_weight = max_adapt_weight + + def calculate_adaptive_weight(self, nll_loss, g_loss, last_layer=None): + if last_layer is not None: + nll_grads = torch.autograd.grad(nll_loss, last_layer, retain_graph=True)[0] + g_grads = torch.autograd.grad(g_loss, last_layer, retain_graph=True)[0] + else: + nll_grads = torch.autograd.grad(nll_loss, self.last_layer[0], retain_graph=True)[0] + g_grads = torch.autograd.grad(g_loss, self.last_layer[0], retain_graph=True)[0] + + d_weight = torch.norm(nll_grads) / (torch.norm(g_grads) + 1e-4) + d_weight = torch.clamp(d_weight, self.min_adapt_weight, self.max_adapt_weight).detach() + d_weight = d_weight * self.discriminator_weight + return d_weight + + def forward(self, codebook_loss, inputs, reconstructions, optimizer_idx, + global_step, last_layer=None, cond=None, split="train"): + rec_loss = torch.abs(inputs.contiguous() - reconstructions.contiguous()) + if self.perceptual_weight > 0: + p_loss = self.perceptual_loss(inputs.contiguous(), reconstructions.contiguous()) + rec_loss = rec_loss + self.perceptual_weight * p_loss + else: + p_loss = torch.tensor([0.0]) + + nll_loss = rec_loss + # nll_loss = torch.sum(nll_loss) / nll_loss.shape[0] + nll_loss = torch.mean(nll_loss) + + # now the GAN part + if optimizer_idx == 0: + # generator update + if cond is None: + assert not self.disc_conditional + logits_fake = self.discriminator(reconstructions.contiguous()) + else: + assert self.disc_conditional + logits_fake = self.discriminator(torch.cat((reconstructions.contiguous(), cond), dim=1)) + g_loss = -torch.mean(logits_fake) + + try: + d_weight = self.calculate_adaptive_weight(nll_loss, g_loss, last_layer=last_layer) + except RuntimeError: + assert not self.training + d_weight = torch.tensor(0.0) + + disc_factor = adopt_weight(self.disc_factor, global_step, threshold=self.discriminator_iter_start) + loss = nll_loss + d_weight * disc_factor * g_loss + self.codebook_weight * codebook_loss.mean() + + log = {"{}/total_loss".format(split): loss.clone().detach().mean(), + "{}/quant_loss".format(split): codebook_loss.detach().mean(), + "{}/nll_loss".format(split): nll_loss.detach().mean(), + "{}/rec_loss".format(split): rec_loss.detach().mean(), + "{}/p_loss".format(split): p_loss.detach().mean(), + "{}/d_weight".format(split): d_weight.detach(), + "{}/disc_factor".format(split): torch.tensor(disc_factor), + "{}/g_loss".format(split): g_loss.detach().mean(), + } + return loss, log + + if optimizer_idx == 1: + # second pass for discriminator update + if cond is None: + logits_real = self.discriminator(inputs.contiguous().detach()) + logits_fake = self.discriminator(reconstructions.contiguous().detach()) + else: + logits_real = self.discriminator(torch.cat((inputs.contiguous().detach(), cond), dim=1)) + logits_fake = self.discriminator(torch.cat((reconstructions.contiguous().detach(), cond), dim=1)) + + disc_factor = adopt_weight(self.disc_factor, global_step, threshold=self.discriminator_iter_start) + d_loss = disc_factor * self.disc_loss(logits_real, logits_fake) + + log = {"{}/disc_loss".format(split): d_loss.clone().detach().mean(), + "{}/logits_real".format(split): logits_real.detach().mean(), + "{}/logits_fake".format(split): logits_fake.detach().mean() + } + return d_loss, log + + +class VQLPAPSWithDiscriminator1dFeats(VQLPAPSWithDiscriminator): + def __init__(self, disc_start, codebook_weight=1.0, pixelloss_weight=1.0, + disc_num_layers=3, disc_in_channels=3, disc_factor=1.0, disc_weight=1.0, + perceptual_weight=1.0, use_actnorm=False, disc_conditional=False, + disc_ndf=64, disc_loss="hinge", min_adapt_weight=0.0, max_adapt_weight=1e4): + super().__init__(disc_start=disc_start, codebook_weight=codebook_weight, + pixelloss_weight=pixelloss_weight, disc_num_layers=disc_num_layers, + disc_in_channels=disc_in_channels, disc_factor=disc_factor, disc_weight=disc_weight, + perceptual_weight=perceptual_weight, use_actnorm=use_actnorm, + disc_conditional=disc_conditional, disc_ndf=disc_ndf, disc_loss=disc_loss, + min_adapt_weight=min_adapt_weight, max_adapt_weight=max_adapt_weight) + + self.discriminator = NLayerDiscriminator1dFeats(input_nc=disc_in_channels, n_layers=disc_num_layers, + use_actnorm=use_actnorm, ndf=disc_ndf).apply(weights_init) + +class VQLPAPSWithDiscriminator1dSpecs(VQLPAPSWithDiscriminator): + def __init__(self, disc_start, codebook_weight=1.0, pixelloss_weight=1.0, + disc_num_layers=3, disc_in_channels=3, disc_factor=1.0, disc_weight=1.0, + perceptual_weight=1.0, use_actnorm=False, disc_conditional=False, + disc_ndf=64, disc_loss="hinge", min_adapt_weight=0.0, max_adapt_weight=1e4): + super().__init__(disc_start=disc_start, codebook_weight=codebook_weight, + pixelloss_weight=pixelloss_weight, disc_num_layers=disc_num_layers, + disc_in_channels=disc_in_channels, disc_factor=disc_factor, disc_weight=disc_weight, + perceptual_weight=perceptual_weight, use_actnorm=use_actnorm, + disc_conditional=disc_conditional, disc_ndf=disc_ndf, disc_loss=disc_loss, + min_adapt_weight=min_adapt_weight, max_adapt_weight=max_adapt_weight) + + self.discriminator = NLayerDiscriminator1dSpecs(input_nc=disc_in_channels, n_layers=disc_num_layers, + use_actnorm=use_actnorm, ndf=disc_ndf).apply(weights_init) + + +if __name__ == '__main__': + from foleycrafter.models.specvqgan.modules.diffusionmodules.model import Decoder, Decoder1d + + optimizer_idx = 0 + loss_config = { + 'disc_conditional': False, + 'disc_start': 30001, + 'disc_weight': 0.8, + 'codebook_weight': 1.0, + } + ddconfig = { + 'ch': 128, + 'num_res_blocks': 2, + 'dropout': 0.0, + 'z_channels': 256, + 'double_z': False, + } + qloss = torch.rand(1, requires_grad=True) + + ## AUDIO + loss_config['disc_in_channels'] = 1 + ddconfig['in_channels'] = 1 + ddconfig['resolution'] = 848 + ddconfig['attn_resolutions'] = [53] + ddconfig['out_ch'] = 1 + ddconfig['ch_mult'] = [1, 1, 2, 2, 4] + decoder = Decoder(**ddconfig) + loss = VQLPAPSWithDiscriminator(**loss_config) + x = torch.rand(16, 1, 80, 848) + # subtracting something which uses dec_conv_out so that it will be in a graph + xrec = torch.rand(16, 1, 80, 848) - decoder.conv_out(torch.rand(16, 128, 80, 848)).mean() + aeloss, log_dict_ae = loss(qloss, x, xrec, optimizer_idx, global_step=0,last_layer=decoder.conv_out.weight) + print(aeloss) + print(log_dict_ae) diff --git a/foleycrafter/models/specvqgan/modules/misc/class_cond.py b/foleycrafter/models/specvqgan/modules/misc/class_cond.py new file mode 100644 index 0000000000000000000000000000000000000000..e7044573e685f24e2db3568148bc20e6f1536a31 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/misc/class_cond.py @@ -0,0 +1,21 @@ +import torch + +class ClassOnlyStage(object): + def __init__(self): + pass + + def eval(self): + return self + + def encode(self, c): + """fake vqmodel interface because self.cond_stage_model should have something + similar to coord.py but even more `dummy`""" + # assert 0.0 <= c.min() and c.max() <= 1.0 + info = None, None, c + return c, None, info + + def decode(self, c): + return c + + def get_input(self, batch, k): + return batch[k].unsqueeze(1).to(memory_format=torch.contiguous_format) diff --git a/foleycrafter/models/specvqgan/modules/misc/coord.py b/foleycrafter/models/specvqgan/modules/misc/coord.py new file mode 100644 index 0000000000000000000000000000000000000000..ee69b0c897b6b382ae673622e420f55e494f5b09 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/misc/coord.py @@ -0,0 +1,31 @@ +import torch + +class CoordStage(object): + def __init__(self, n_embed, down_factor): + self.n_embed = n_embed + self.down_factor = down_factor + + def eval(self): + return self + + def encode(self, c): + """fake vqmodel interface""" + assert 0.0 <= c.min() and c.max() <= 1.0 + b,ch,h,w = c.shape + assert ch == 1 + + c = torch.nn.functional.interpolate(c, scale_factor=1/self.down_factor, + mode="area") + c = c.clamp(0.0, 1.0) + c = self.n_embed*c + c_quant = c.round() + c_ind = c_quant.to(dtype=torch.long) + + info = None, None, c_ind + return c_quant, None, info + + def decode(self, c): + c = c/self.n_embed + c = torch.nn.functional.interpolate(c, scale_factor=self.down_factor, + mode="nearest") + return c diff --git a/foleycrafter/models/specvqgan/modules/misc/feat_cluster.py b/foleycrafter/models/specvqgan/modules/misc/feat_cluster.py new file mode 100644 index 0000000000000000000000000000000000000000..47b0527d25bdcdf56e7598c7522ac8f9a4c25854 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/misc/feat_cluster.py @@ -0,0 +1,83 @@ +import os +from glob import glob + +import joblib +import numpy as np +import torch +from sklearn.cluster import MiniBatchKMeans +from torch.utils.data import DataLoader +from tqdm import tqdm +from train import instantiate_from_config + + +class FeatClusterStage(object): + + def __init__(self, num_clusters=None, cached_kmeans_path=None, feats_dataset_config=None, num_workers=None): + if cached_kmeans_path is not None and os.path.exists(cached_kmeans_path): + print(f'Precalculated Clusterer already exists, loading from {cached_kmeans_path}') + self.clusterer = joblib.load(cached_kmeans_path) + elif feats_dataset_config is not None: + self.clusterer = self.load_or_precalculate_kmeans(num_clusters, feats_dataset_config, num_workers) + else: + raise Exception('Neither `feats_dataset_config` nor `cached_kmeans_path` are defined') + + def eval(self): + return self + + def encode(self, c): + # c_quant: cluster centers, c_ind: cluster index + + B, D, T = c.shape + # (B*T, D) <- (B, T, D) <- (B, D, T) + c_flat = c.permute(0, 2, 1).view(B*T, D).cpu().numpy() + + c_ind = self.clusterer.predict(c_flat) + c_quant = self.clusterer.cluster_centers_[c_ind] + + c_ind = torch.from_numpy(c_ind).to(c.device) + c_quant = torch.from_numpy(c_quant).to(c.device) + + c_ind = c_ind.long().unsqueeze(-1) + c_quant = c_quant.view(B, T, D).permute(0, 2, 1) + + info = None, None, c_ind + # (B, D, T), (), ((), (768, 1024), (768, 1)) + return c_quant, None, info + + def decode(self, c): + return c + + def get_input(self, batch, k): + x = batch[k] + x = x.permute(0, 2, 1).to(memory_format=torch.contiguous_format) + return x.float() + + def load_or_precalculate_kmeans(self, num_clusters, dataset_cfg, num_workers): + print(f'Calculating clustering K={num_clusters}') + batch_size = 64 + dataset_name = dataset_cfg.target.split('.')[-1] + cached_path = os.path.join('./specvqgan/modules/misc/', f'kmeans_K{num_clusters}_{dataset_name}.sklearn') + feat_depth = dataset_cfg.params.condition_dataset_cfg.feat_depth + feat_crop_len = dataset_cfg.params.condition_dataset_cfg.feat_crop_len + + feat_loading_dset = instantiate_from_config(dataset_cfg) + feat_loading_dset = DataLoader(feat_loading_dset, batch_size, num_workers=num_workers, shuffle=True) + + clusterer = MiniBatchKMeans(num_clusters, batch_size=batch_size*feat_crop_len, random_state=0) + + for item in tqdm(feat_loading_dset): + batch = item['feature'].reshape(-1, feat_depth).float().numpy() + clusterer.partial_fit(batch) + + joblib.dump(clusterer, cached_path) + print(f'Saved the calculated Clusterer @ {cached_path}') + return clusterer + + +if __name__ == '__main__': + from omegaconf import OmegaConf + + config = OmegaConf.load('./configs/vggsound_featcluster_transformer.yaml') + config.model.params.first_stage_config.params.ckpt_path = './logs/2021-05-19T22-16-54_vggsound_specs_vqgan/checkpoints/epoch_39.ckpt' + model = instantiate_from_config(config.model.params.cond_stage_config) + print(model) diff --git a/foleycrafter/models/specvqgan/modules/misc/feats_class.py b/foleycrafter/models/specvqgan/modules/misc/feats_class.py new file mode 100644 index 0000000000000000000000000000000000000000..72980972f919ceb63b3aeadb118e86c97ceb7f2b --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/misc/feats_class.py @@ -0,0 +1,28 @@ +import torch + +class FeatsClassStage(object): + def __init__(self): + pass + + def eval(self): + return self + + def encode(self, c): + """fake vqmodel interface because self.cond_stage_model should have something + similar to coord.py but even more `dummy`""" + # assert 0.0 <= c.min() and c.max() <= 1.0 + info = None, None, c + return c, None, info + + def decode(self, c): + return c + + def get_input(self, batch: dict, keys: dict) -> dict: + out = {} + for k in keys: + if k == 'target': + out[k] = batch[k].unsqueeze(1) + elif k == 'feature': + out[k] = batch[k].float().permute(0, 2, 1) + out[k] = out[k].to(memory_format=torch.contiguous_format) + return out diff --git a/foleycrafter/models/specvqgan/modules/misc/raw_feats.py b/foleycrafter/models/specvqgan/modules/misc/raw_feats.py new file mode 100644 index 0000000000000000000000000000000000000000..96b13f250abb0ac878026b207d1857084411caa5 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/misc/raw_feats.py @@ -0,0 +1,23 @@ +import torch + +class RawFeatsStage(object): + def __init__(self): + pass + + def eval(self): + return self + + def encode(self, c): + """fake vqmodel interface because self.cond_stage_model should have something + similar to coord.py but even more `dummy`""" + # assert 0.0 <= c.min() and c.max() <= 1.0 + info = None, None, c + return c, None, info + + def decode(self, c): + return c + + def get_input(self, batch, k): + x = batch[k] + x = x.permute(0, 2, 1).to(memory_format=torch.contiguous_format) + return x.float() diff --git a/foleycrafter/models/specvqgan/modules/transformer/mingpt.py b/foleycrafter/models/specvqgan/modules/transformer/mingpt.py new file mode 100644 index 0000000000000000000000000000000000000000..d59f0fea2111fa8039d20cb3c04cd677b85d4115 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/transformer/mingpt.py @@ -0,0 +1,535 @@ +""" +taken from: https://github.com/karpathy/minGPT/ +GPT model: +- the initial stem consists of a combination of token encoding and a positional encoding +- the meat of it is a uniform sequence of Transformer blocks + - each Transformer is a sequential combination of a 1-hidden-layer MLP block and a self-attention block + - all blocks feed into a central residual pathway similar to resnets +- the final decoder is a linear projection into a vanilla Softmax classifier +""" + +import math +import logging + +import torch +import torch.nn as nn +from torch.nn import functional as F +import sys +sys.path.insert(0, '.') # nopep8 +from train import instantiate_from_config + +logger = logging.getLogger(__name__) + + +class GPTConfig: + """ base GPT config, params common to all GPT versions """ + embd_pdrop = 0.1 + resid_pdrop = 0.1 + attn_pdrop = 0.1 + + def __init__(self, vocab_size, block_size, **kwargs): + self.vocab_size = vocab_size + self.block_size = block_size + for k,v in kwargs.items(): + setattr(self, k, v) + + +class GPT1Config(GPTConfig): + """ GPT-1 like network roughly 125M params """ + n_layer = 12 + n_head = 12 + n_embd = 768 + + +class GPT2Config(GPTConfig): + """ GPT-2 like network roughly 1.5B params """ + # TODO + + +class CausalSelfAttention(nn.Module): + """ + A vanilla multi-head masked self-attention layer with a projection at the end. + It is possible to use torch.nn.MultiheadAttention here but I am including an + explicit implementation here to show that there is nothing too scary here. + """ + + def __init__(self, config): + super().__init__() + assert config.n_embd % config.n_head == 0 + # key, query, value projections for all heads + self.key = nn.Linear(config.n_embd, config.n_embd) + self.query = nn.Linear(config.n_embd, config.n_embd) + self.value = nn.Linear(config.n_embd, config.n_embd) + # regularization + self.attn_drop = nn.Dropout(config.attn_pdrop) + self.resid_drop = nn.Dropout(config.resid_pdrop) + # output projection + self.proj = nn.Linear(config.n_embd, config.n_embd) + # causal mask to ensure that attention is only applied to the left in the input sequence + mask = torch.tril(torch.ones(config.block_size, + config.block_size)) + if hasattr(config, "n_unmasked"): + mask[:config.n_unmasked, :config.n_unmasked] = 1 + self.register_buffer("mask", mask.view(1, 1, config.block_size, config.block_size)) + self.n_head = config.n_head + + def forward(self, x, layer_past=None): + B, T, C = x.size() + + # calculate query, key, values for all heads in batch and move head forward to be the batch dim + k = self.key(x).view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs) + q = self.query(x).view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs) + v = self.value(x).view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs) + + # causal self-attention; Self-attend: (B, nh, T, hs) x (B, nh, hs, T) -> (B, nh, T, T) + att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1))) + att = att.masked_fill(self.mask[:,:,:T,:T] == 0, float('-inf')) + att = F.softmax(att, dim=-1) + y = self.attn_drop(att) @ v # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs) + y = y.transpose(1, 2).contiguous().view(B, T, C) # re-assemble all head outputs side by side + + # output projection + y = self.resid_drop(self.proj(y)) + + return y, att + + +class Block(nn.Module): + """ an unassuming Transformer block """ + def __init__(self, config): + super().__init__() + self.ln1 = nn.LayerNorm(config.n_embd) + self.ln2 = nn.LayerNorm(config.n_embd) + self.attn = CausalSelfAttention(config) + self.mlp = nn.Sequential( + nn.Linear(config.n_embd, 4 * config.n_embd), + nn.GELU(), # nice + nn.Linear(4 * config.n_embd, config.n_embd), + nn.Dropout(config.resid_pdrop), + ) + + def forward(self, x): + # x = x + self.attn(self.ln1(x)) + + # x is a tuple (x, attention) + x, _ = x + res = x + x = self.ln1(x) + x, att = self.attn(x) + x = res + x + + x = x + self.mlp(self.ln2(x)) + + return x, att + + +class GPT(nn.Module): + """ the full GPT language model, with a context size of block_size """ + def __init__(self, vocab_size, block_size, n_layer=12, n_head=8, n_embd=256, + embd_pdrop=0., resid_pdrop=0., attn_pdrop=0., n_unmasked=0): + super().__init__() + config = GPTConfig(vocab_size=vocab_size, block_size=block_size, + embd_pdrop=embd_pdrop, resid_pdrop=resid_pdrop, attn_pdrop=attn_pdrop, + n_layer=n_layer, n_head=n_head, n_embd=n_embd, + n_unmasked=n_unmasked) + # input embedding stem + self.tok_emb = nn.Embedding(config.vocab_size, config.n_embd) + self.pos_emb = nn.Parameter(torch.zeros(1, config.block_size, config.n_embd)) + self.drop = nn.Dropout(config.embd_pdrop) + # transformer + self.blocks = nn.Sequential(*[Block(config) for _ in range(config.n_layer)]) + # decoder head + self.ln_f = nn.LayerNorm(config.n_embd) + self.head = nn.Linear(config.n_embd, config.vocab_size, bias=False) + self.block_size = config.block_size + self.apply(self._init_weights) + self.config = config + logger.info("number of parameters: %e", sum(p.numel() for p in self.parameters())) + + def get_block_size(self): + return self.block_size + + def _init_weights(self, module): + if isinstance(module, (nn.Linear, nn.Embedding)): + module.weight.data.normal_(mean=0.0, std=0.02) + if isinstance(module, nn.Linear) and module.bias is not None: + module.bias.data.zero_() + elif isinstance(module, nn.LayerNorm): + module.bias.data.zero_() + module.weight.data.fill_(1.0) + + def forward(self, idx, embeddings=None, targets=None): + # forward the GPT model + token_embeddings = self.tok_emb(idx) # each index maps to a (learnable) vector + + if embeddings is not None: # prepend explicit embeddings + token_embeddings = torch.cat((embeddings, token_embeddings), dim=1) + + t = token_embeddings.shape[1] + assert t <= self.block_size, "Cannot forward, model block size is exhausted." + position_embeddings = self.pos_emb[:, :t, :] # each position maps to a (learnable) vector + x = self.drop(token_embeddings + position_embeddings) + + # returns only last layer attention + # giving tuple (x, None) just because Sequential takes a single input but outputs two (x, atttention). + # att is (B, H, T, T) + x, att = self.blocks((x, None)) + x = self.ln_f(x) + logits = self.head(x) + + # if we are given some desired targets also calculate the loss + loss = None + if targets is not None: + loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1)) + + return logits, loss, att + + +class DummyGPT(nn.Module): + # for debugging + def __init__(self, add_value=1): + super().__init__() + self.add_value = add_value + + def forward(self, idx): + raise NotImplementedError('Model should output attention') + return idx + self.add_value, None + + +class CodeGPT(nn.Module): + """Takes in semi-embeddings""" + def __init__(self, vocab_size, block_size, in_channels, n_layer=12, n_head=8, n_embd=256, + embd_pdrop=0., resid_pdrop=0., attn_pdrop=0., n_unmasked=0): + super().__init__() + config = GPTConfig(vocab_size=vocab_size, block_size=block_size, + embd_pdrop=embd_pdrop, resid_pdrop=resid_pdrop, attn_pdrop=attn_pdrop, + n_layer=n_layer, n_head=n_head, n_embd=n_embd, + n_unmasked=n_unmasked) + # input embedding stem + self.tok_emb = nn.Linear(in_channels, config.n_embd) + self.pos_emb = nn.Parameter(torch.zeros(1, config.block_size, config.n_embd)) + self.drop = nn.Dropout(config.embd_pdrop) + # transformer + self.blocks = nn.Sequential(*[Block(config) for _ in range(config.n_layer)]) + # decoder head + self.ln_f = nn.LayerNorm(config.n_embd) + self.head = nn.Linear(config.n_embd, config.vocab_size, bias=False) + self.block_size = config.block_size + self.apply(self._init_weights) + self.config = config + logger.info("number of parameters: %e", sum(p.numel() for p in self.parameters())) + + def get_block_size(self): + return self.block_size + + def _init_weights(self, module): + if isinstance(module, (nn.Linear, nn.Embedding)): + module.weight.data.normal_(mean=0.0, std=0.02) + if isinstance(module, nn.Linear) and module.bias is not None: + module.bias.data.zero_() + elif isinstance(module, nn.LayerNorm): + module.bias.data.zero_() + module.weight.data.fill_(1.0) + elif isinstance(module, (nn.Conv1d, nn.Conv2d)): + torch.nn.init.xavier_uniform(module.weight) + if module.bias is not None: + module.bias.data.fill_(0.01) + + def forward(self, idx, embeddings=None, targets=None): + raise NotImplementedError('Model should output attention') + # forward the GPT model + token_embeddings = self.tok_emb(idx) # each index maps to a (learnable) vector + + if embeddings is not None: # prepend explicit embeddings + token_embeddings = torch.cat((embeddings, token_embeddings), dim=1) + + t = token_embeddings.shape[1] + assert t <= self.block_size, "Cannot forward, model block size is exhausted." + position_embeddings = self.pos_emb[:, :t, :] # each position maps to a (learnable) vector + x = self.drop(token_embeddings + position_embeddings) + x = self.blocks(x) + x = self.ln_f(x) + logits = self.head(x) + + # if we are given some desired targets also calculate the loss + loss = None + if targets is not None: + loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1)) + + return logits, loss + +class GPTFeats(GPT): + + def __init__(self, feat_embedding_config, GPT_config): + super().__init__(**GPT_config) + # patching the config by removing the default parameters for Conv1d + if feat_embedding_config.target.split('.')[-1] in ['LSTM', 'GRU']: + for p in ['in_channels', 'out_channels', 'padding', 'kernel_size']: + if p in feat_embedding_config.params: + feat_embedding_config.params.pop(p) + self.embedder = instantiate_from_config(config=feat_embedding_config) + if isinstance(self.embedder, nn.Linear): + print('Checkout cond_transformer.configure_optimizers. Make sure not to use decay with Linear') + + def forward(self, idx, feats): + if isinstance(self.embedder, nn.Linear): + feats = feats.permute(0, 2, 1) + feats = self.embedder(feats) + elif isinstance(self.embedder, (nn.LSTM, nn.GRU)): + feats = feats.permute(0, 2, 1) + feats, _ = self.embedder(feats) + elif isinstance(self.embedder, (nn.Conv1d, nn.Identity)): + # (B, D', T) <- (B, D, T) + feats = self.embedder(feats) + # (B, T, D') <- (B, T, D) + feats = feats.permute(0, 2, 1) + else: + raise NotImplementedError + # calling forward from super + return super().forward(idx, embeddings=feats) + +class GPTFeatsPosEnc(GPT): + def __init__(self, feat_embedding_config, GPT_config, PosEnc_config): + super().__init__(**GPT_config) + # patching the config by removing the default parameters for Conv1d + if feat_embedding_config.target.split('.')[-1] in ['LSTM', 'GRU']: + for p in ['in_channels', 'out_channels', 'padding', 'kernel_size']: + if p in feat_embedding_config.params: + feat_embedding_config.params.pop(p) + self.embedder = instantiate_from_config(config=feat_embedding_config) + + self.pos_emb_vis = nn.Parameter(torch.zeros(1, PosEnc_config['block_size_v'], PosEnc_config['n_embd'])) + self.pos_emb_aud = nn.Parameter(torch.zeros(1, PosEnc_config['block_size_a'], PosEnc_config['n_embd'])) + + if isinstance(self.embedder, nn.Linear): + print('Checkout cond_transformer.configure_optimizers. Make sure not to use decay with Linear') + + def foward(self, idx, feats): + if isinstance(self.embedder, nn.Linear): + feats = feats.permute(0, 2, 1) + feats = self.embedder(feats) + elif isinstance(self.embedder, (nn.LSTM, nn.GRU)): + feats = feats.permute(0, 2, 1) + feats, _ = self.embedder(feats) + elif isinstance(self.embedder, (nn.Conv1d, nn.Identity)): + # (B, D', T) <- (B, D, T) + feats = self.embedder(feats) + # (B, T, D') <- (B, T, D) + feats = feats.permute(0, 2, 1) + else: + raise NotImplementedError + # calling forward from super + # forward the GPT model + token_embeddings = self.tok_emb(idx) # each index maps to a (learnable) vector + + if feats is not None: # prepend explicit feats + token_embeddings = torch.cat((feats, token_embeddings), dim=1) + + t = token_embeddings.shape[1] + assert t <= self.block_size, "Cannot forward, model block size is exhausted." + vis_t = self.pos_emb_vis.shape[1] + position_embeddings = torch.cat([self.pos_emb_vis, self.pos_emb_aud[:, :t-vis_t, :]]) + x = self.drop(token_embeddings + position_embeddings) + + # returns only last layer attention + # giving tuple (x, None) just because Sequential takes a single input but outputs two (x, atttention). + # att is (B, H, T, T) + x, att = self.blocks((x, None)) + x = self.ln_f(x) + logits = self.head(x) + + # if we are given some desired targets also calculate the loss + loss = None + + return logits, loss, att + + + +class GPTClass(GPT): + + def __init__(self, token_embedding_config, GPT_config): + super().__init__(**GPT_config) + self.embedder = instantiate_from_config(config=token_embedding_config) + + def forward(self, idx, token): + token = self.embedder(token) + # calling forward from super + return super().forward(idx, embeddings=token) + +class GPTFeatsClass(GPT): + + def __init__(self, feat_embedding_config, token_embedding_config, GPT_config): + super().__init__(**GPT_config) + + # patching the config by removing the default parameters for Conv1d + if feat_embedding_config.target.split('.')[-1] in ['LSTM', 'GRU']: + for p in ['in_channels', 'out_channels', 'padding', 'kernel_size']: + if p in feat_embedding_config.params: + feat_embedding_config.params.pop(p) + + self.feat_embedder = instantiate_from_config(config=feat_embedding_config) + self.cls_embedder = instantiate_from_config(config=token_embedding_config) + + if isinstance(self.feat_embedder, nn.Linear): + print('Checkout cond_transformer.configure_optimizers. Make sure not to use decay with Linear') + + def forward(self, idx, feats_token_dict: dict): + feats = feats_token_dict['feature'] + token = feats_token_dict['target'] + + # Features. Output size: (B, T, D') + if isinstance(self.feat_embedder, nn.Linear): + feats = feats.permute(0, 2, 1) + feats = self.feat_embedder(feats) + elif isinstance(self.feat_embedder, (nn.LSTM, nn.GRU)): + feats = feats.permute(0, 2, 1) + feats, _ = self.feat_embedder(feats) + elif isinstance(self.feat_embedder, (nn.Conv1d, nn.Identity)): + # (B, D', T) <- (B, D, T) + feats = self.feat_embedder(feats) + # (B, T, D') <- (B, T, D) + feats = feats.permute(0, 2, 1) + else: + raise NotImplementedError + + # Class. Output size: (B, 1, D') + token = self.cls_embedder(token) + + # Concat + condition_emb = torch.cat([feats, token], dim=1) + + # calling forward from super + return super().forward(idx, embeddings=condition_emb) + + +#### sampling utils + +def top_k_logits(logits, k): + v, ix = torch.topk(logits, k) + out = logits.clone() + out[out < v[:, [-1]]] = -float('Inf') + return out + +@torch.no_grad() +def sample(model, x, steps, temperature=1.0, sample=False, top_k=None): + """ + take a conditioning sequence of indices in x (of shape (b,t)) and predict the next token in + the sequence, feeding the predictions back into the model each time. Clearly the sampling + has quadratic complexity unlike an RNN that is only linear, and has a finite context window + of block_size, unlike an RNN that has an infinite context window. + """ + block_size = model.get_block_size() + model.eval() + for k in range(steps): + x_cond = x if x.size(1) <= block_size else x[:, -block_size:] # crop context if needed + raise NotImplementedError('v-iashin: the model outputs (logits, loss, attention)') + logits, _ = model(x_cond) + # pluck the logits at the final step and scale by temperature + logits = logits[:, -1, :] / temperature + # optionally crop probabilities to only the top k options + if top_k is not None: + logits = top_k_logits(logits, top_k) + # apply softmax to convert to probabilities + probs = F.softmax(logits, dim=-1) + # sample from the distribution or take the most likely + if sample: + ix = torch.multinomial(probs, num_samples=1) + else: + _, ix = torch.topk(probs, k=1, dim=-1) + # append to the sequence and continue + x = torch.cat((x, ix), dim=1) + + return x + + + +#### clustering utils + +class KMeans(nn.Module): + def __init__(self, ncluster=512, nc=3, niter=10): + super().__init__() + self.ncluster = ncluster + self.nc = nc + self.niter = niter + self.shape = (3,32,32) + self.register_buffer("C", torch.zeros(self.ncluster,nc)) + self.register_buffer('initialized', torch.tensor(0, dtype=torch.uint8)) + + def is_initialized(self): + return self.initialized.item() == 1 + + @torch.no_grad() + def initialize(self, x): + N, D = x.shape + assert D == self.nc, D + c = x[torch.randperm(N)[:self.ncluster]] # init clusters at random + for i in range(self.niter): + # assign all pixels to the closest codebook element + a = ((x[:, None, :] - c[None, :, :])**2).sum(-1).argmin(1) + # move each codebook element to be the mean of the pixels that assigned to it + c = torch.stack([x[a==k].mean(0) for k in range(self.ncluster)]) + # re-assign any poorly positioned codebook elements + nanix = torch.any(torch.isnan(c), dim=1) + ndead = nanix.sum().item() + print('done step %d/%d, re-initialized %d dead clusters' % (i+1, self.niter, ndead)) + c[nanix] = x[torch.randperm(N)[:ndead]] # re-init dead clusters + + self.C.copy_(c) + self.initialized.fill_(1) + + + def forward(self, x, reverse=False, shape=None): + if not reverse: + # flatten + bs,c,h,w = x.shape + assert c == self.nc + x = x.reshape(bs,c,h*w,1) + C = self.C.permute(1,0) + C = C.reshape(1,c,1,self.ncluster) + a = ((x-C)**2).sum(1).argmin(-1) # bs, h*w indices + return a + else: + # flatten + bs, HW = x.shape + """ + c = self.C.reshape( 1, self.nc, 1, self.ncluster) + c = c[bs*[0],:,:,:] + c = c[:,:,HW*[0],:] + x = x.reshape(bs, 1, HW, 1) + x = x[:,3*[0],:,:] + x = torch.gather(c, dim=3, index=x) + """ + x = self.C[x] + x = x.permute(0,2,1) + shape = shape if shape is not None else self.shape + x = x.reshape(bs, *shape) + + return x + + +if __name__ == '__main__': + import torch + from omegaconf import OmegaConf + import numpy as np + from tqdm import tqdm + + device = torch.device('cuda:2') + torch.cuda.set_device(device) + + cfg = OmegaConf.load('./configs/vggsound_transformer.yaml') + + model = instantiate_from_config(cfg.model.params.transformer_config) + model = model.to(device) + + mel_num = cfg.data.params.mel_num + spec_crop_len = cfg.data.params.spec_crop_len + feat_depth = cfg.data.params.feat_depth + feat_crop_len = cfg.data.params.feat_crop_len + + gcd = np.gcd(mel_num, spec_crop_len) + z_idx_size = (2, int(mel_num / gcd) * int(spec_crop_len / gcd)) + + for i in tqdm(range(300)): + z_indices = torch.randint(0, cfg.model.params.transformer_config.params.GPT_config.vocab_size, z_idx_size).to(device) + c = torch.rand(2, feat_depth, feat_crop_len).to(device) + logits, loss, att = model(z_indices[:, :-1], feats=c) diff --git a/foleycrafter/models/specvqgan/modules/transformer/permuter.py b/foleycrafter/models/specvqgan/modules/transformer/permuter.py new file mode 100644 index 0000000000000000000000000000000000000000..94375a55efc302ec04da16676f19046e58aefa05 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/transformer/permuter.py @@ -0,0 +1,295 @@ +import torch +import torch.nn as nn +import numpy as np + +TO_WARN_USER_ONCE = True + +class AbstractPermuter(nn.Module): + def __init__(self, *args, **kwargs): + super().__init__() + def forward(self, x, reverse=False): + raise NotImplementedError + + +class Identity(AbstractPermuter): + def __init__(self): + super().__init__() + + def forward(self, x, reverse=False): + return x + +class ColumnMajor(AbstractPermuter): + '''Useful for spectrograms which are from left to right (features, time)''' + def __init__(self, H, W): + super().__init__() + self.H = H + self.W = W + idx = self.make_idx(H, W) + self.register_buffer('forward_shuffle_idx', idx) + self.register_buffer('backward_shuffle_idx', torch.argsort(idx)) + + def forward(self, x, reverse=False): + B, L = x.shape + L_idx = len(self.forward_shuffle_idx) + if L > L_idx: + # an ugly patch for "infinite" sampling because self.*_shuffle_idx are shorter + # otherwise even uglier patch in other places. 'if' is triggered only on sampling. + assert L % L_idx == 0 and L / L_idx == int(L / L_idx), f'L: {L}, L_idx: {L_idx}' + W_scale = L // L_idx + # print(f'Permuter is making a guess on the temp scale: {W_scale}. Ignore on "infinite" sampling') + idx = self.make_idx(self.H, self.W * W_scale) + if not reverse: + return x[:, idx] + else: + return x[:, torch.argsort(idx)] + else: + if not reverse: + return x[:, self.forward_shuffle_idx] + else: + return x[:, self.backward_shuffle_idx] + + def make_idx(self, H, W): + idx = np.arange(H * W).reshape(H, W) + idx = idx.T + idx = torch.tensor(idx.ravel()) + return idx + +class Subsample(AbstractPermuter): + def __init__(self, H, W): + super().__init__() + C = 1 + indices = np.arange(H*W).reshape(C,H,W) + while min(H, W) > 1: + indices = indices.reshape(C,H//2,2,W//2,2) + indices = indices.transpose(0,2,4,1,3) + indices = indices.reshape(C*4,H//2, W//2) + H = H//2 + W = W//2 + C = C*4 + assert H == W == 1 + idx = torch.tensor(indices.ravel()) + self.register_buffer('forward_shuffle_idx', + nn.Parameter(idx, requires_grad=False)) + self.register_buffer('backward_shuffle_idx', + nn.Parameter(torch.argsort(idx), requires_grad=False)) + + def forward(self, x, reverse=False): + if not reverse: + return x[:, self.forward_shuffle_idx] + else: + return x[:, self.backward_shuffle_idx] + + +def mortonify(i, j): + """(i,j) index to linear morton code""" + i = np.uint64(i) + j = np.uint64(j) + + z = np.uint(0) + + for pos in range(32): + z = (z | + ((j & (np.uint64(1) << np.uint64(pos))) << np.uint64(pos)) | + ((i & (np.uint64(1) << np.uint64(pos))) << np.uint64(pos+1)) + ) + return z + + +class ZCurve(AbstractPermuter): + def __init__(self, H, W): + super().__init__() + reverseidx = [np.int64(mortonify(i,j)) for i in range(H) for j in range(W)] + idx = np.argsort(reverseidx) + idx = torch.tensor(idx) + reverseidx = torch.tensor(reverseidx) + self.register_buffer('forward_shuffle_idx', + idx) + self.register_buffer('backward_shuffle_idx', + reverseidx) + + def forward(self, x, reverse=False): + if not reverse: + return x[:, self.forward_shuffle_idx] + else: + return x[:, self.backward_shuffle_idx] + + +class SpiralOut(AbstractPermuter): + def __init__(self, H, W): + super().__init__() + assert H == W + size = W + indices = np.arange(size*size).reshape(size,size) + + i0 = size//2 + j0 = size//2-1 + + i = i0 + j = j0 + + idx = [indices[i0, j0]] + step_mult = 0 + for c in range(1, size//2+1): + step_mult += 1 + # steps left + for k in range(step_mult): + i = i - 1 + j = j + idx.append(indices[i, j]) + + # step down + for k in range(step_mult): + i = i + j = j + 1 + idx.append(indices[i, j]) + + step_mult += 1 + if c < size//2: + # step right + for k in range(step_mult): + i = i + 1 + j = j + idx.append(indices[i, j]) + + # step up + for k in range(step_mult): + i = i + j = j - 1 + idx.append(indices[i, j]) + else: + # end reached + for k in range(step_mult-1): + i = i + 1 + idx.append(indices[i, j]) + + assert len(idx) == size*size + idx = torch.tensor(idx) + self.register_buffer('forward_shuffle_idx', idx) + self.register_buffer('backward_shuffle_idx', torch.argsort(idx)) + + def forward(self, x, reverse=False): + if not reverse: + return x[:, self.forward_shuffle_idx] + else: + return x[:, self.backward_shuffle_idx] + + +class SpiralIn(AbstractPermuter): + def __init__(self, H, W): + super().__init__() + assert H == W + size = W + indices = np.arange(size*size).reshape(size,size) + + i0 = size//2 + j0 = size//2-1 + + i = i0 + j = j0 + + idx = [indices[i0, j0]] + step_mult = 0 + for c in range(1, size//2+1): + step_mult += 1 + # steps left + for k in range(step_mult): + i = i - 1 + j = j + idx.append(indices[i, j]) + + # step down + for k in range(step_mult): + i = i + j = j + 1 + idx.append(indices[i, j]) + + step_mult += 1 + if c < size//2: + # step right + for k in range(step_mult): + i = i + 1 + j = j + idx.append(indices[i, j]) + + # step up + for k in range(step_mult): + i = i + j = j - 1 + idx.append(indices[i, j]) + else: + # end reached + for k in range(step_mult-1): + i = i + 1 + idx.append(indices[i, j]) + + assert len(idx) == size*size + idx = idx[::-1] + idx = torch.tensor(idx) + self.register_buffer('forward_shuffle_idx', idx) + self.register_buffer('backward_shuffle_idx', torch.argsort(idx)) + + def forward(self, x, reverse=False): + if not reverse: + return x[:, self.forward_shuffle_idx] + else: + return x[:, self.backward_shuffle_idx] + + +class Random(nn.Module): + def __init__(self, H, W): + super().__init__() + indices = np.random.RandomState(1).permutation(H*W) + idx = torch.tensor(indices.ravel()) + self.register_buffer('forward_shuffle_idx', idx) + self.register_buffer('backward_shuffle_idx', torch.argsort(idx)) + + def forward(self, x, reverse=False): + if not reverse: + return x[:, self.forward_shuffle_idx] + else: + return x[:, self.backward_shuffle_idx] + + +class AlternateParsing(AbstractPermuter): + def __init__(self, H, W): + super().__init__() + indices = np.arange(W*H).reshape(H,W) + for i in range(1, H, 2): + indices[i, :] = indices[i, ::-1] + idx = indices.flatten() + assert len(idx) == H*W + idx = torch.tensor(idx) + self.register_buffer('forward_shuffle_idx', idx) + self.register_buffer('backward_shuffle_idx', torch.argsort(idx)) + + def forward(self, x, reverse=False): + if not reverse: + return x[:, self.forward_shuffle_idx] + else: + return x[:, self.backward_shuffle_idx] + + +if __name__ == "__main__": + p0 = AlternateParsing(16, 16) + print(p0.forward_shuffle_idx) + print(p0.backward_shuffle_idx) + + x = torch.randint(0, 768, size=(11, 256)) + y = p0(x) + xre = p0(y, reverse=True) + assert torch.equal(x, xre) + + p1 = SpiralOut(2, 2) + print(p1.forward_shuffle_idx) + print(p1.backward_shuffle_idx) + x = torch.randint(0, 768, size=(11, 2*2)) + y = p1(x) + xre = p1(y, reverse=True) + assert torch.equal(x, xre) + + p2 = ColumnMajor(5, 53) + print(p2.forward_shuffle_idx) + print(p2.backward_shuffle_idx) + x = torch.randint(0, 768, size=(11, 5*53)) + xre = p2(p2(x), reverse=True) + assert torch.equal(x, xre) diff --git a/foleycrafter/models/specvqgan/modules/video_model/r2plus1d_18.py b/foleycrafter/models/specvqgan/modules/video_model/r2plus1d_18.py new file mode 100644 index 0000000000000000000000000000000000000000..e526d7cb47bfcc50ba1c57ffb9e790c55a4f41fb --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/video_model/r2plus1d_18.py @@ -0,0 +1,124 @@ +import sys + +import torch +import torch.nn as nn +import torchvision + +sys.path.insert(0, '.') # nopep8 +from foleycrafter.models.specvqgan.modules.video_model.resnet import r2plus1d_18 + +FPS = 15 + +class Identity(nn.Module): + def __init__(self): + super(Identity, self).__init__() + + def forward(self, x): + return x + +class r2plus1d18KeepTemp(nn.Module): + + def __init__(self, pretrained=True): + super().__init__() + + self.model = r2plus1d_18(pretrained=pretrained) + + self.model.layer2[0].conv1[0][3] = nn.Conv3d(230, 128, kernel_size=(3, 1, 1), + stride=(1, 1, 1), padding=(1, 0, 0), bias=False) + self.model.layer2[0].downsample = nn.Sequential( + nn.Conv3d(64, 128, kernel_size=(1, 1, 1), stride=(1, 2, 2), bias=False), + nn.BatchNorm3d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) + ) + self.model.layer3[0].conv1[0][3] = nn.Conv3d(460, 256, kernel_size=(3, 1, 1), + stride=(1, 1, 1), padding=(1, 0, 0), bias=False) + self.model.layer3[0].downsample = nn.Sequential( + nn.Conv3d(128, 256, kernel_size=(1, 1, 1), stride=(1, 2, 2), bias=False), + nn.BatchNorm3d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) + ) + self.model.layer4[0].conv1[0][3] = nn.Conv3d(921, 512, kernel_size=(3, 1, 1), + stride=(1, 1, 1), padding=(1, 0, 0), bias=False) + self.model.layer4[0].downsample = nn.Sequential( + nn.Conv3d(256, 512, kernel_size=(1, 1, 1), stride=(1, 2, 2), bias=False), + nn.BatchNorm3d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) + ) + self.model.avgpool = nn.AdaptiveAvgPool3d((None, 1, 1)) + self.model.fc = Identity() + + with torch.no_grad(): + rand_input = torch.randn((1, 3, 30, 112, 112)) + output = self.model(rand_input).detach().cpu() + print('Validate Video feature shape: ', output.shape) # (1, 512, 30) + + def forward(self, x): + N = x.shape[0] + return self.model(x).reshape(N, 512, -1) + + def eval(self): + return self + + def encode(self, c): + info = None, None, c + return c, None, info + + def decode(self, c): + return c + + def get_input(self, batch, k, drop_cond=False): + x = batch[k].cuda() + x = x.permute(0, 2, 1, 3, 4).to(memory_format=torch.contiguous_format) # (N, 3, T, 112, 112) + T = x.shape[2] + if drop_cond: + output = self.model(x) # (N, 512, T) + else: + cond_x = x[:, :, :T//2] # (N, 3, T//2, 112, 112) + x = x[:, :, T//2:] # (N, 3, T//2, 112, 112) + cond_feat = self.model(cond_x) # (N, 512, T//2) + feat = self.model(x) # (N, 512, T//2) + output = torch.cat([cond_feat, feat], dim=-1) # (N, 512, T) + assert output.shape[2] == T + return output + + +class resnet50(nn.Module): + + def __init__(self, pretrained=True): + super().__init__() + self.model = torchvision.models.resnet50(pretrained=pretrained) + self.model.fc = nn.Identity() + # freeze resnet 50 model + for params in self.model.parameters(): + params.requires_grad = False + + def forward(self, x): + N = x.shape[0] + return self.model(x).reshape(N, 2048) + + def eval(self): + return self + + def encode(self, c): + info = None, None, c + return c, None, info + + def decode(self, c): + return c + + def get_input(self, batch, k, drop_cond=False): + x = batch[k].cuda() + x = x.permute(0, 2, 1, 3, 4).to(memory_format=torch.contiguous_format) # (N, 3, T, 112, 112) + T = x.shape[2] + feats = [] + for t in range(T): + xt = x[:, :, t] + feats.append(self.model(xt)) + output = torch.stack(feats, dim=-1) + assert output.shape[2] == T + return output + + + +if __name__ == '__main__': + model = r2plus1d18KeepTemp(False).cuda() + x = {'input': torch.randn((1, 60, 3, 112, 112))} + out = model.get_input(x, 'input') + print(out.shape) diff --git a/foleycrafter/models/specvqgan/modules/video_model/resnet.py b/foleycrafter/models/specvqgan/modules/video_model/resnet.py new file mode 100644 index 0000000000000000000000000000000000000000..b5023327f7e53a59fa940983cccb84483a91d581 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/video_model/resnet.py @@ -0,0 +1,344 @@ +import torch.nn as nn + +from torchvision.models.utils import load_state_dict_from_url + + +__all__ = ['r3d_18', 'mc3_18', 'r2plus1d_18'] + +model_urls = { + 'r3d_18': 'https://download.pytorch.org/models/r3d_18-b3b3357e.pth', + 'mc3_18': 'https://download.pytorch.org/models/mc3_18-a90a0ba3.pth', + 'r2plus1d_18': 'https://download.pytorch.org/models/r2plus1d_18-91a641e6.pth', +} + + +class Conv3DSimple(nn.Conv3d): + def __init__(self, + in_planes, + out_planes, + midplanes=None, + stride=1, + padding=1): + + super(Conv3DSimple, self).__init__( + in_channels=in_planes, + out_channels=out_planes, + kernel_size=(3, 3, 3), + stride=stride, + padding=padding, + bias=False) + + @staticmethod + def get_downsample_stride(stride): + return stride, stride, stride + + +class Conv2Plus1D(nn.Sequential): + + def __init__(self, + in_planes, + out_planes, + midplanes, + stride=1, + padding=1): + super(Conv2Plus1D, self).__init__( + nn.Conv3d(in_planes, midplanes, kernel_size=(1, 3, 3), + stride=(1, stride, stride), padding=(0, padding, padding), + bias=False), + nn.BatchNorm3d(midplanes), + nn.ReLU(inplace=True), + nn.Conv3d(midplanes, out_planes, kernel_size=(3, 1, 1), + stride=(stride, 1, 1), padding=(padding, 0, 0), + bias=False)) + + @staticmethod + def get_downsample_stride(stride): + return stride, stride, stride + + +class Conv3DNoTemporal(nn.Conv3d): + + def __init__(self, + in_planes, + out_planes, + midplanes=None, + stride=1, + padding=1): + + super(Conv3DNoTemporal, self).__init__( + in_channels=in_planes, + out_channels=out_planes, + kernel_size=(1, 3, 3), + stride=(1, stride, stride), + padding=(0, padding, padding), + bias=False) + + @staticmethod + def get_downsample_stride(stride): + return 1, stride, stride + + +class BasicBlock(nn.Module): + + expansion = 1 + + def __init__(self, inplanes, planes, conv_builder, stride=1, downsample=None): + midplanes = (inplanes * planes * 3 * 3 * 3) // (inplanes * 3 * 3 + 3 * planes) + + super(BasicBlock, self).__init__() + self.conv1 = nn.Sequential( + conv_builder(inplanes, planes, midplanes, stride), + nn.BatchNorm3d(planes), + nn.ReLU(inplace=True) + ) + self.conv2 = nn.Sequential( + conv_builder(planes, planes, midplanes), + nn.BatchNorm3d(planes) + ) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.conv2(out) + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, conv_builder, stride=1, downsample=None): + + super(Bottleneck, self).__init__() + midplanes = (inplanes * planes * 3 * 3 * 3) // (inplanes * 3 * 3 + 3 * planes) + + # 1x1x1 + self.conv1 = nn.Sequential( + nn.Conv3d(inplanes, planes, kernel_size=1, bias=False), + nn.BatchNorm3d(planes), + nn.ReLU(inplace=True) + ) + # Second kernel + self.conv2 = nn.Sequential( + conv_builder(planes, planes, midplanes, stride), + nn.BatchNorm3d(planes), + nn.ReLU(inplace=True) + ) + + # 1x1x1 + self.conv3 = nn.Sequential( + nn.Conv3d(planes, planes * self.expansion, kernel_size=1, bias=False), + nn.BatchNorm3d(planes * self.expansion) + ) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.conv2(out) + out = self.conv3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class BasicStem(nn.Sequential): + """The default conv-batchnorm-relu stem + """ + def __init__(self): + super(BasicStem, self).__init__( + nn.Conv3d(3, 64, kernel_size=(3, 7, 7), stride=(1, 2, 2), + padding=(1, 3, 3), bias=False), + nn.BatchNorm3d(64), + nn.ReLU(inplace=True)) + + +class R2Plus1dStem(nn.Sequential): + """R(2+1)D stem is different than the default one as it uses separated 3D convolution + """ + def __init__(self): + super(R2Plus1dStem, self).__init__( + nn.Conv3d(3, 45, kernel_size=(1, 7, 7), + stride=(1, 2, 2), padding=(0, 3, 3), + bias=False), + nn.BatchNorm3d(45), + nn.ReLU(inplace=True), + nn.Conv3d(45, 64, kernel_size=(3, 1, 1), + stride=(1, 1, 1), padding=(1, 0, 0), + bias=False), + nn.BatchNorm3d(64), + nn.ReLU(inplace=True)) + + +class VideoResNet(nn.Module): + + def __init__(self, block, conv_makers, layers, + stem, num_classes=400, + zero_init_residual=False): + """Generic resnet video generator. + + Args: + block (nn.Module): resnet building block + conv_makers (list(functions)): generator function for each layer + layers (List[int]): number of blocks per layer + stem (nn.Module, optional): Resnet stem, if None, defaults to conv-bn-relu. Defaults to None. + num_classes (int, optional): Dimension of the final FC layer. Defaults to 400. + zero_init_residual (bool, optional): Zero init bottleneck residual BN. Defaults to False. + """ + super(VideoResNet, self).__init__() + self.inplanes = 64 + + self.stem = stem() + + self.layer1 = self._make_layer(block, conv_makers[0], 64, layers[0], stride=1) + self.layer2 = self._make_layer(block, conv_makers[1], 128, layers[1], stride=2) + self.layer3 = self._make_layer(block, conv_makers[2], 256, layers[2], stride=2) + self.layer4 = self._make_layer(block, conv_makers[3], 512, layers[3], stride=2) + + self.avgpool = nn.AdaptiveAvgPool3d((1, 1, 1)) + self.fc = nn.Linear(512 * block.expansion, num_classes) + + # init weights + self._initialize_weights() + + if zero_init_residual: + for m in self.modules(): + if isinstance(m, Bottleneck): + nn.init.constant_(m.bn3.weight, 0) + + def forward(self, x): + x = self.stem(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + # Flatten the layer to fc + # x = x.flatten(1) + # x = self.fc(x) + N = x.shape[0] + x = x.squeeze() + if N == 1: + x = x[None] + + return x + + def _make_layer(self, block, conv_builder, planes, blocks, stride=1): + downsample = None + + if stride != 1 or self.inplanes != planes * block.expansion: + ds_stride = conv_builder.get_downsample_stride(stride) + downsample = nn.Sequential( + nn.Conv3d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=ds_stride, bias=False), + nn.BatchNorm3d(planes * block.expansion) + ) + layers = [] + layers.append(block(self.inplanes, planes, conv_builder, stride, downsample)) + + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes, conv_builder)) + + return nn.Sequential(*layers) + + def _initialize_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv3d): + nn.init.kaiming_normal_(m.weight, mode='fan_out', + nonlinearity='relu') + if m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm3d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.constant_(m.bias, 0) + + +def _video_resnet(arch, pretrained=False, progress=True, **kwargs): + model = VideoResNet(**kwargs) + + if pretrained: + state_dict = load_state_dict_from_url(model_urls[arch], + progress=progress) + model.load_state_dict(state_dict) + return model + + +def r3d_18(pretrained=False, progress=True, **kwargs): + """Construct 18 layer Resnet3D model as in + https://arxiv.org/abs/1711.11248 + + Args: + pretrained (bool): If True, returns a model pre-trained on Kinetics-400 + progress (bool): If True, displays a progress bar of the download to stderr + + Returns: + nn.Module: R3D-18 network + """ + + return _video_resnet('r3d_18', + pretrained, progress, + block=BasicBlock, + conv_makers=[Conv3DSimple] * 4, + layers=[2, 2, 2, 2], + stem=BasicStem, **kwargs) + + +def mc3_18(pretrained=False, progress=True, **kwargs): + """Constructor for 18 layer Mixed Convolution network as in + https://arxiv.org/abs/1711.11248 + + Args: + pretrained (bool): If True, returns a model pre-trained on Kinetics-400 + progress (bool): If True, displays a progress bar of the download to stderr + + Returns: + nn.Module: MC3 Network definition + """ + return _video_resnet('mc3_18', + pretrained, progress, + block=BasicBlock, + conv_makers=[Conv3DSimple] + [Conv3DNoTemporal] * 3, + layers=[2, 2, 2, 2], + stem=BasicStem, **kwargs) + + +def r2plus1d_18(pretrained=False, progress=True, **kwargs): + """Constructor for the 18 layer deep R(2+1)D network as in + https://arxiv.org/abs/1711.11248 + + Args: + pretrained (bool): If True, returns a model pre-trained on Kinetics-400 + progress (bool): If True, displays a progress bar of the download to stderr + + Returns: + nn.Module: R(2+1)D-18 network + """ + return _video_resnet('r2plus1d_18', + pretrained, progress, + block=BasicBlock, + conv_makers=[Conv2Plus1D] * 4, + layers=[2, 2, 2, 2], + stem=R2Plus1dStem, **kwargs) diff --git a/foleycrafter/models/specvqgan/modules/vqvae/quantize.py b/foleycrafter/models/specvqgan/modules/vqvae/quantize.py new file mode 100644 index 0000000000000000000000000000000000000000..296df15e68c5810368d24cec1ce3abf9db1dd237 --- /dev/null +++ b/foleycrafter/models/specvqgan/modules/vqvae/quantize.py @@ -0,0 +1,131 @@ +import torch +import torch.nn as nn + + +class VectorQuantizer(nn.Module): + """ + see https://github.com/MishaLaskin/vqvae/blob/d761a999e2267766400dc646d82d3ac3657771d4/models/quantizer.py + ____________________________________________ + Discretization bottleneck part of the VQ-VAE. + Inputs: + - n_e : number of embeddings + - e_dim : dimension of embedding + - beta : commitment cost used in loss term, beta * ||z_e(x)-sg[e]||^2 + _____________________________________________ + """ + + def __init__(self, n_e, e_dim, beta): + super(VectorQuantizer, self).__init__() + self.n_e = n_e + self.e_dim = e_dim + self.beta = beta + + self.embedding = nn.Embedding(self.n_e, self.e_dim) + self.embedding.weight.data.uniform_(-1.0 / self.n_e, 1.0 / self.n_e) + + # better inheritence properties (so that when VectorQuantizer1d() inherits it, only these will be + # changed) + self.permute_order_in = [0, 2, 3, 1] + self.permute_order_out = [0, 3, 1, 2] + + def forward(self, z): + """ + Inputs the output of the encoder network z and maps it to a discrete + one-hot vector that is the index of the closest embedding vector e_j + z (continuous) -> z_q (discrete) + 2d: z.shape = (batch, channel, height, width) + 1d: z.shape = (batch, channel, time) + quantization pipeline: + 1. get encoder input 2d: (B,C,H,W) or 1d: (B, C, T) + 2. flatten input to 2d: (B*H*W,C) or 1d: (B*T, C) + """ + # reshape z -> (batch, height, width, channel) or (batch, time, channel) and flatten + z = z.permute(self.permute_order_in).contiguous() + z_flattened = z.view(-1, self.e_dim) + # distances from z to embeddings e_j (z - e)^2 = z^2 + e^2 - 2 e * z + + d = torch.sum(z_flattened ** 2, dim=1, keepdim=True) + \ + torch.sum(self.embedding.weight**2, dim=1) - 2 * \ + torch.matmul(z_flattened, self.embedding.weight.t()) + + ## could possible replace this here + # #\start... + # find closest encodings + min_encoding_indices = torch.argmin(d, dim=1).unsqueeze(1) + + min_encodings = torch.zeros(min_encoding_indices.shape[0], self.n_e).to(z) + min_encodings.scatter_(1, min_encoding_indices, 1) + + # dtype min encodings: torch.float32 + # min_encodings shape: torch.Size([2048, 512]) + # min_encoding_indices.shape: torch.Size([2048, 1]) + + # get quantized latent vectors + z_q = torch.matmul(min_encodings, self.embedding.weight).view(z.shape) + #.........\end + + # with: + # .........\start + #min_encoding_indices = torch.argmin(d, dim=1) + #z_q = self.embedding(min_encoding_indices) + # ......\end......... (TODO) + + # compute loss for embedding + loss = torch.mean((z_q.detach() - z) ** 2) + self.beta * torch.mean((z_q - z.detach()) ** 2) + + # preserve gradients + z_q = z + (z_q - z).detach() + + # perplexity + e_mean = torch.mean(min_encodings, dim=0) + perplexity = torch.exp(-torch.sum(e_mean * torch.log(e_mean + 1e-10))) + + # reshape back to match original input shape + z_q = z_q.permute(self.permute_order_out).contiguous() + + return z_q, loss, (perplexity, min_encodings, min_encoding_indices) + + def get_codebook_entry(self, indices, shape): + # shape specifying (batch, height, width, channel) + # TODO: check for more easy handling with nn.Embedding + min_encodings = torch.zeros(indices.shape[0], self.n_e).to(indices) + min_encodings.scatter_(1, indices[:, None], 1) + + # get quantized latent vectors + z_q = torch.matmul(min_encodings.float(), self.embedding.weight) + + if shape is not None: + z_q = z_q.view(shape) + + # reshape back to match original input shape + z_q = z_q.permute(self.permute_order_out).contiguous() + + return z_q + +class VectorQuantizer1d(VectorQuantizer): + + def __init__(self, n_embed, embed_dim, beta=0.25): + super().__init__(n_embed, embed_dim, beta) + self.permute_order_in = [0, 2, 1] + self.permute_order_out = [0, 2, 1] + + +if __name__ == '__main__': + quantize = VectorQuantizer1d(n_embed=1024, embed_dim=256, beta=0.25) + + # 1d Input (features) + enc_outputs = torch.rand(6, 256, 53) + quant, emb_loss, info = quantize(enc_outputs) + print(quant.shape) + + quantize = VectorQuantizer(n_e=1024, e_dim=256, beta=0.25) + + # Audio + enc_outputs = torch.rand(4, 256, 5, 53) + quant, emb_loss, info = quantize(enc_outputs) + print(quant.shape) + + # Image + enc_outputs = torch.rand(4, 256, 16, 16) + quant, emb_loss, info = quantize(enc_outputs) + print(quant.shape) diff --git a/foleycrafter/models/specvqgan/onset_baseline/config/__init__.py b/foleycrafter/models/specvqgan/onset_baseline/config/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..eaee0a833230c377934c809dc4a1c65c562002fe --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/config/__init__.py @@ -0,0 +1 @@ +from .config import init_args \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/config/config.py b/foleycrafter/models/specvqgan/onset_baseline/config/config.py new file mode 100644 index 0000000000000000000000000000000000000000..631ef2653af7737b6a0bbcfbe1f4a40dad7b8d00 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/config/config.py @@ -0,0 +1,51 @@ +import argparse +import numpy as np + +def init_args(return_parser=False): + parser = argparse.ArgumentParser(description="""Configure""") + + # basic configuration + parser.add_argument('--exp', type=str, default='test101', + help='checkpoint folder') + + parser.add_argument('--epochs', type=int, default=100, + help='number of total epochs to run (default: 90)') + + parser.add_argument('--start_epoch', default=0, type=int, + help='manual epoch number (useful on restarts) (default: 0)') + parser.add_argument('--resume', default='', type=str, + metavar='PATH', help='path to checkpoint (default: None)') + parser.add_argument('--resume_optim', default=False, action='store_true') + parser.add_argument('--save_step', default=1, type=int) + parser.add_argument('--valid_step', default=1, type=int) + + + # Dataloader parameter + parser.add_argument('--max_sample', default=-1, type=int) + parser.add_argument('--repeat', default=1, type=int) + parser.add_argument('--num_workers', type=int, default=8) + parser.add_argument('--batch_size', default=24, type=int) + + # network parameters + parser.add_argument('--pretrained', default=False, action='store_true') + + # optimizer parameters + parser.add_argument('--lr', default=1e-4, type=float, help='learning rate') + parser.add_argument('--momentum', type=float, default=0.9) + parser.add_argument('--weight_decay', default=5e-4, + type=float, help='weight decay (default: 5e-4)') + parser.add_argument('--optim', type=str, default='Adam', + choices=['SGD', 'Adam']) + parser.add_argument('--schedule', type=str, default='cos', choices=['none', 'cos', 'step'], required=False) + + parser.add_argument('--aug_img', default=False, action='store_true') + parser.add_argument('--test_mode', default=False, action='store_true') + + + if return_parser: + return parser + + # global args + args = parser.parse_args() + + return args diff --git a/foleycrafter/models/specvqgan/onset_baseline/data/__init__.py b/foleycrafter/models/specvqgan/onset_baseline/data/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4eb49348adeb79491b7c8df13f89234951836d97 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/data/__init__.py @@ -0,0 +1,2 @@ +from .greatesthit import * +from .impactset import * \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/data/greatesthit.py b/foleycrafter/models/specvqgan/onset_baseline/data/greatesthit.py new file mode 100644 index 0000000000000000000000000000000000000000..cef9381dbf179941fd82ae9c8069f872c958a8ed --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/data/greatesthit.py @@ -0,0 +1,158 @@ +from data import * +import pdb +from utils import sound, sourcesep +import csv +import glob +import h5py +import io +import json +import librosa +import numpy as np +import os +import pickle +from PIL import Image +from PIL import ImageFilter +import random +import scipy +import soundfile as sf +import time +from tqdm import tqdm +import glob +import cv2 + +import torch +import torch.nn as nn +import torchaudio +import torchvision.transforms as transforms +# import kornia as K +import sys +sys.path.append('..') + + +class GreatestHitDataset(object): + def __init__(self, args, split='train'): + self.split = split + if split == 'train': + list_sample = './data/greatesthit_train_2.00.json' + elif split == 'val': + list_sample = './data/greatesthit_valid_2.00.json' + elif split == 'test': + list_sample = './data/greatesthit_test_2.00.json' + + # save args parameter + self.repeat = args.repeat if split == 'train' else 1 + self.max_sample = args.max_sample + + self.video_transform = transforms.Compose( + self.generate_video_transform(args)) + + if isinstance(list_sample, str): + with open(list_sample, "r") as f: + self.list_sample = json.load(f) + + if self.max_sample > 0: + self.list_sample = self.list_sample[0:self.max_sample] + self.list_sample = self.list_sample * self.repeat + + random.seed(1234) + np.random.seed(1234) + num_sample = len(self.list_sample) + if self.split == 'train': + random.shuffle(self.list_sample) + + # self.class_dist = self.unbalanced_dist() + print('Greatesthit Dataloader: # sample of {}: {}'.format(self.split, num_sample)) + + + def __getitem__(self, index): + # import pdb; pdb.set_trace() + info = self.list_sample[index].split('_')[0] + video_path = os.path.join('data', 'greatesthit', 'greatesthit_processed', info) + frame_path = os.path.join(video_path, 'frames') + audio_path = os.path.join(video_path, 'audio') + audio_path = glob.glob(f"{audio_path}/*.wav")[0] + # Unused, consider remove + meta_path = os.path.join(video_path, 'hit_record.json') + if os.path.exists(meta_path): + with open(meta_path, "r") as f: + meta_dict = json.load(f) + + audio, audio_sample_rate = sf.read(audio_path, start=0, stop=1000, dtype='float64', always_2d=True) + frame_rate = 15 + duration = 2.0 + frame_list = glob.glob(f'{frame_path}/*.jpg') + frame_list.sort() + + hit_time = float(self.list_sample[index].split('_')[-1]) / 22050 + if self.split == 'train': + frame_start = hit_time * frame_rate + np.random.randint(10) - 5 + frame_start = max(frame_start, 0) + frame_start = min(frame_start, len(frame_list) - duration * frame_rate) + + else: + frame_start = hit_time * frame_rate + frame_start = max(frame_start, 0) + frame_start = min(frame_start, len(frame_list) - duration * frame_rate) + frame_start = int(frame_start) + + frame_list = frame_list[frame_start: int( + frame_start + np.ceil(duration * frame_rate))] + audio_start = int(frame_start / frame_rate * audio_sample_rate) + audio_end = int(audio_start + duration * audio_sample_rate) + + imgs = self.read_image(frame_list) + audio, audio_rate = sf.read(audio_path, start=audio_start, stop=audio_end, dtype='float64', always_2d=True) + audio = audio.mean(-1) + + onsets = librosa.onset.onset_detect(y=audio, sr=audio_rate, units='time', delta=0.3) + onsets = np.rint(onsets * frame_rate).astype(int) + onsets[onsets>29] = 29 + label = torch.zeros(len(frame_list)) + label[onsets] = 1 + + batch = { + 'frames': imgs, + 'label': label + } + return batch + + def getitem_test(self, index): + self.__getitem__(index) + + def __len__(self): + return len(self.list_sample) + + + def read_image(self, frame_list): + imgs = [] + convert_tensor = transforms.ToTensor() + for img_path in frame_list: + image = Image.open(img_path).convert('RGB') + image = convert_tensor(image) + imgs.append(image.unsqueeze(0)) + # (T, C, H ,W) + imgs = torch.cat(imgs, dim=0).squeeze() + imgs = self.video_transform(imgs) + imgs = imgs.permute(1, 0, 2, 3) + # (C, T, H ,W) + return imgs + + def generate_video_transform(self, args): + resize_funct = transforms.Resize((128, 128)) + if self.split == 'train': + crop_funct = transforms.RandomCrop( + (112, 112)) + color_funct = transforms.ColorJitter( + brightness=0.1, contrast=0.1, saturation=0, hue=0) + else: + crop_funct = transforms.CenterCrop( + (112, 112)) + color_funct = transforms.Lambda(lambda img: img) + + vision_transform_list = [ + resize_funct, + crop_funct, + color_funct, + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + ] + return vision_transform_list diff --git a/foleycrafter/models/specvqgan/onset_baseline/data/impactset.py b/foleycrafter/models/specvqgan/onset_baseline/data/impactset.py new file mode 100644 index 0000000000000000000000000000000000000000..c6d3d737176c2b8a3753785edd3951e6baac174b --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/data/impactset.py @@ -0,0 +1,145 @@ +from data import * +import pdb +from utils import sound, sourcesep +import csv +import glob +import h5py +import io +import json +import librosa +import numpy as np +import os +import pickle +from PIL import Image +from PIL import ImageFilter +import random +import scipy +import soundfile as sf +import time +from tqdm import tqdm +import glob +import cv2 + +import torch +import torch.nn as nn +import torchaudio +import torchvision.transforms as transforms +# import kornia as K +import sys +sys.path.append('..') + + +class CountixAVDataset(object): + def __init__(self, args, split='train'): + self.split = split + if split == 'train': + list_sample = './data/countixAV_train.json' + elif split == 'val': + list_sample = './data/countixAV_val.json' + elif split == 'test': + list_sample = './data/countixAV_test.json' + + # save args parameter + self.repeat = args.repeat if split == 'train' else 1 + self.max_sample = args.max_sample + + self.video_transform = transforms.Compose( + self.generate_video_transform(args)) + + if isinstance(list_sample, str): + with open(list_sample, "r") as f: + self.list_sample = json.load(f) + + if self.max_sample > 0: + self.list_sample = self.list_sample[0:self.max_sample] + self.list_sample = self.list_sample * self.repeat + + random.seed(1234) + np.random.seed(1234) + num_sample = len(self.list_sample) + if self.split == 'train': + random.shuffle(self.list_sample) + + # self.class_dist = self.unbalanced_dist() + print('Greatesthit Dataloader: # sample of {}: {}'.format(self.split, num_sample)) + + + def __getitem__(self, index): + # import pdb; pdb.set_trace() + info = self.list_sample[index] + video_path = os.path.join('data', 'ImpactSet', 'impactset-proccess-resize', info) + frame_path = os.path.join(video_path, 'frames') + audio_path = os.path.join(video_path, 'audio') + audio_path = glob.glob(f"{audio_path}/*_denoised.wav")[0] + + audio, audio_sample_rate = sf.read(audio_path, start=0, stop=1000, dtype='float64', always_2d=True) + frame_rate = 15 + duration = 2.0 + frame_list = glob.glob(f'{frame_path}/*.jpg') + frame_list.sort() + + frame_start = random.randint(0, len(frame_list)) + frame_start = min(frame_start, len(frame_list) - duration * frame_rate) + frame_start = int(frame_start) + + frame_list = frame_list[frame_start: int( + frame_start + np.ceil(duration * frame_rate))] + audio_start = int(frame_start / frame_rate * audio_sample_rate) + audio_end = int(audio_start + duration * audio_sample_rate) + + imgs = self.read_image(frame_list) + audio, audio_rate = sf.read(audio_path, start=audio_start, stop=audio_end, dtype='float64', always_2d=True) + audio = audio.mean(-1) + + onsets = librosa.onset.onset_detect(y=audio, sr=audio_rate, units='time', delta=0.3) + onsets = np.rint(onsets * frame_rate).astype(int) + onsets[onsets>29] = 29 + label = torch.zeros(len(frame_list)) + label[onsets] = 1 + + batch = { + 'frames': imgs, + 'label': label + } + return batch + + def getitem_test(self, index): + self.__getitem__(index) + + def __len__(self): + return len(self.list_sample) + + + def read_image(self, frame_list): + imgs = [] + convert_tensor = transforms.ToTensor() + for img_path in frame_list: + image = Image.open(img_path).convert('RGB') + image = convert_tensor(image) + imgs.append(image.unsqueeze(0)) + # (T, C, H ,W) + imgs = torch.cat(imgs, dim=0).squeeze() + imgs = self.video_transform(imgs) + imgs = imgs.permute(1, 0, 2, 3) + # (C, T, H ,W) + return imgs + + def generate_video_transform(self, args): + resize_funct = transforms.Resize((128, 128)) + if self.split == 'train': + crop_funct = transforms.RandomCrop( + (112, 112)) + color_funct = transforms.ColorJitter( + brightness=0.1, contrast=0.1, saturation=0, hue=0) + else: + crop_funct = transforms.CenterCrop( + (112, 112)) + color_funct = transforms.Lambda(lambda img: img) + + vision_transform_list = [ + resize_funct, + crop_funct, + color_funct, + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + ] + return vision_transform_list \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/data/transforms.py b/foleycrafter/models/specvqgan/onset_baseline/data/transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..21834486ee6324245b49a961fc963a5af927e91a --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/data/transforms.py @@ -0,0 +1,298 @@ +import torch +import torchaudio +import torchaudio.functional +from torchvision import transforms +import torchvision.transforms.functional as F +import torch.nn as nn +from PIL import Image +import numpy as np +import math +import random + + +class ResizeShortSide(object): + def __init__(self, size): + super().__init__() + self.size = size + + def __call__(self, x): + ''' + x must be PIL.Image + ''' + w, h = x.size + short_side = min(w, h) + w_target = int((w / short_side) * self.size) + h_target = int((h / short_side) * self.size) + return x.resize((w_target, h_target)) + + +class RandomResizedCrop3D(nn.Module): + """Crop the given series of images to random size and aspect ratio. + The image can be a PIL Images or a Tensor, in which case it is expected + to have [N, ..., H, W] shape, where ... means an arbitrary number of leading dimensions + + A crop of random size (default: of 0.08 to 1.0) of the original size and a random + aspect ratio (default: of 3/4 to 4/3) of the original aspect ratio is made. This crop + is finally resized to given size. + This is popularly used to train the Inception networks. + + Args: + size (int or sequence): expected output size of each edge. If size is an + int instead of sequence like (h, w), a square output size ``(size, size)`` is + made. If provided a tuple or list of length 1, it will be interpreted as (size[0], size[0]). + scale (tuple of float): range of size of the origin size cropped + ratio (tuple of float): range of aspect ratio of the origin aspect ratio cropped. + interpolation (int): Desired interpolation enum defined by `filters`_. + Default is ``PIL.Image.BILINEAR``. If input is Tensor, only ``PIL.Image.NEAREST``, ``PIL.Image.BILINEAR`` + and ``PIL.Image.BICUBIC`` are supported. + """ + + def __init__(self, size, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.), interpolation=transforms.InterpolationMode.BILINEAR): + super().__init__() + if isinstance(size, tuple) and len(size) == 2: + self.size = size + else: + self.size = (size, size) + + self.interpolation = interpolation + self.scale = scale + self.ratio = ratio + + @staticmethod + def get_params(img, scale, ratio): + """Get parameters for ``crop`` for a random sized crop. + + Args: + img (PIL Image or Tensor): Input image. + scale (list): range of scale of the origin size cropped + ratio (list): range of aspect ratio of the origin aspect ratio cropped + + Returns: + tuple: params (i, j, h, w) to be passed to ``crop`` for a random + sized crop. + """ + width, height = img.size + area = height * width + + for _ in range(10): + target_area = area * \ + torch.empty(1).uniform_(scale[0], scale[1]).item() + log_ratio = torch.log(torch.tensor(ratio)) + aspect_ratio = torch.exp( + torch.empty(1).uniform_(log_ratio[0], log_ratio[1]) + ).item() + + w = int(round(math.sqrt(target_area * aspect_ratio))) + h = int(round(math.sqrt(target_area / aspect_ratio))) + + if 0 < w <= width and 0 < h <= height: + i = torch.randint(0, height - h + 1, size=(1,)).item() + j = torch.randint(0, width - w + 1, size=(1,)).item() + return i, j, h, w + + # Fallback to central crop + in_ratio = float(width) / float(height) + if in_ratio < min(ratio): + w = width + h = int(round(w / min(ratio))) + elif in_ratio > max(ratio): + h = height + w = int(round(h * max(ratio))) + else: # whole image + w = width + h = height + i = (height - h) // 2 + j = (width - w) // 2 + return i, j, h, w + + def forward(self, imgs): + """ + Args: + img (PIL Image or Tensor): Image to be cropped and resized. + + Returns: + PIL Image or Tensor: Randomly cropped and resized image. + """ + i, j, h, w = self.get_params(imgs[0], self.scale, self.ratio) + return [F.resized_crop(img, i, j, h, w, self.size, self.interpolation) for img in imgs] + + +class Resize3D(object): + def __init__(self, size): + super().__init__() + self.size = size + + def __call__(self, imgs): + ''' + x must be PIL.Image + ''' + return [x.resize((self.size, self.size)) for x in imgs] + + +class RandomHorizontalFlip3D(object): + def __init__(self, p=0.5): + super().__init__() + self.p = p + + def __call__(self, imgs): + ''' + x must be PIL.Image + ''' + if np.random.rand() < self.p: + return [x.transpose(Image.FLIP_LEFT_RIGHT) for x in imgs] + else: + return imgs + + +class ColorJitter3D(torch.nn.Module): + """Randomly change the brightness, contrast and saturation of an image. + + Args: + brightness (float or tuple of float (min, max)): How much to jitter brightness. + brightness_factor is chosen uniformly from [max(0, 1 - brightness), 1 + brightness] + or the given [min, max]. Should be non negative numbers. + contrast (float or tuple of float (min, max)): How much to jitter contrast. + contrast_factor is chosen uniformly from [max(0, 1 - contrast), 1 + contrast] + or the given [min, max]. Should be non negative numbers. + saturation (float or tuple of float (min, max)): How much to jitter saturation. + saturation_factor is chosen uniformly from [max(0, 1 - saturation), 1 + saturation] + or the given [min, max]. Should be non negative numbers. + hue (float or tuple of float (min, max)): How much to jitter hue. + hue_factor is chosen uniformly from [-hue, hue] or the given [min, max]. + Should have 0<= hue <= 0.5 or -0.5 <= min <= max <= 0.5. + """ + + def __init__(self, brightness=0, contrast=0, saturation=0, hue=0): + super().__init__() + self.brightness = (1-brightness, 1+brightness) + self.contrast = (1-contrast, 1+contrast) + self.saturation = (1-saturation, 1+saturation) + self.hue = (0-hue, 0+hue) + + @staticmethod + def get_params(brightness, contrast, saturation, hue): + """Get a randomized transform to be applied on image. + + Arguments are same as that of __init__. + + Returns: + Transform which randomly adjusts brightness, contrast and + saturation in a random order. + """ + tfs = [] + + if brightness is not None: + brightness_factor = random.uniform(brightness[0], brightness[1]) + tfs.append(transforms.Lambda( + lambda img: F.adjust_brightness(img, brightness_factor))) + + if contrast is not None: + contrast_factor = random.uniform(contrast[0], contrast[1]) + tfs.append(transforms.Lambda( + lambda img: F.adjust_contrast(img, contrast_factor))) + + if saturation is not None: + saturation_factor = random.uniform(saturation[0], saturation[1]) + tfs.append(transforms.Lambda( + lambda img: F.adjust_saturation(img, saturation_factor))) + + if hue is not None: + hue_factor = random.uniform(hue[0], hue[1]) + tfs.append(transforms.Lambda( + lambda img: F.adjust_hue(img, hue_factor))) + + random.shuffle(tfs) + transform = transforms.Compose(tfs) + + return transform + + def forward(self, imgs): + """ + Args: + img (PIL Image or Tensor): Input image. + + Returns: + PIL Image or Tensor: Color jittered image. + """ + transform = self.get_params( + self.brightness, self.contrast, self.saturation, self.hue) + return [transform(img) for img in imgs] + + +class ToTensor3D(object): + def __init__(self): + super().__init__() + + def __call__(self, imgs): + ''' + x must be PIL.Image + ''' + return [F.to_tensor(img) for img in imgs] + + +class Normalize3D(object): + def __init__(self, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], inplace=False): + super().__init__() + self.mean = mean + self.std = std + self.inplace = inplace + + def __call__(self, imgs): + ''' + x must be PIL.Image + ''' + return [F.normalize(img, self.mean, self.std, self.inplace) for img in imgs] + + +class CenterCrop3D(object): + def __init__(self, size): + super().__init__() + self.size = size + + def __call__(self, imgs): + ''' + x must be PIL.Image + ''' + return [F.center_crop(img, self.size) for img in imgs] + + +class FrequencyMasking(object): + def __init__(self, freq_mask_param: int, iid_masks: bool = False): + super().__init__() + self.masking = torchaudio.transforms.FrequencyMasking(freq_mask_param, iid_masks) + + def __call__(self, item): + if 'cond_image' in item.keys(): + batched_spec = torch.stack( + [torch.tensor(item['image']), torch.tensor(item['cond_image'])], dim=0 + )[:, None] # (2, 1, H, W) + masked = self.masking(batched_spec).numpy() + item['image'] = masked[0, 0] + item['cond_image'] = masked[1, 0] + elif 'image' in item.keys(): + inp = torch.tensor(item['image']) + item['image'] = self.masking(inp).numpy() + else: + raise NotImplementedError() + return item + + +class TimeMasking(object): + def __init__(self, time_mask_param: int, iid_masks: bool = False): + super().__init__() + self.masking = torchaudio.transforms.TimeMasking(time_mask_param, iid_masks) + + def __call__(self, item): + if 'cond_image' in item.keys(): + batched_spec = torch.stack( + [torch.tensor(item['image']), torch.tensor(item['cond_image'])], dim=0 + )[:, None] # (2, 1, H, W) + masked = self.masking(batched_spec).numpy() + item['image'] = masked[0, 0] + item['cond_image'] = masked[1, 0] + elif 'image' in item.keys(): + inp = torch.tensor(item['image']) + item['image'] = self.masking(inp).numpy() + else: + raise NotImplementedError() + return item diff --git a/foleycrafter/models/specvqgan/onset_baseline/demo.ipynb b/foleycrafter/models/specvqgan/onset_baseline/demo.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..98889a002e251dcbc0dc5fd2d4e81f2a8b0bc7f2 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/demo.ipynb @@ -0,0 +1,352 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Change audio by detecting onset \n", + "This notebook contains a method that could change the target video sound with a given audio." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load packages" + ] + }, + { + "cell_type": "code", + "execution_count": 118, + "metadata": {}, + "outputs": [], + "source": [ + "import IPython\n", + "import os\n", + "import numpy as np\n", + "from moviepy.editor import *\n", + "import librosa\n", + "from IPython.display import Audio\n", + "from IPython.display import Video" + ] + }, + { + "cell_type": "code", + "execution_count": 119, + "metadata": {}, + "outputs": [], + "source": [ + "# Read videos\n", + "origin_video_path = 'data/target.mp4'\n", + "conditional_video_path = 'data/conditional.mp4'\n", + "# conditional_video_path = 'data/dog_bark.mp4'\n", + "\n", + "ori_videoclip = VideoFileClip(origin_video_path)\n", + "con_videoclip = VideoFileClip(conditional_video_path)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 120, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Video(origin_video_path, width=640)" + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 121, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Video(conditional_video_path, width=640)" + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "metadata": {}, + "outputs": [], + "source": [ + "# get the audio track from video\n", + "ori_audioclip = ori_videoclip.audio\n", + "ori_audio, ori_sr = ori_audioclip.to_soundarray(), ori_audioclip.fps\n", + "con_audioclip = con_videoclip.audio\n", + "con_audio, con_sr = con_audioclip.to_soundarray(), con_audioclip.fps\n", + "\n", + "ori_audio = ori_audio.mean(-1)\n", + "con_audio = con_audio.mean(-1)\n", + "\n", + "target_sr = 22050\n", + "ori_audio = librosa.resample(ori_audio, orig_sr=ori_sr, target_sr=target_sr)\n", + "con_audio = librosa.resample(con_audio, orig_sr=con_sr, target_sr=target_sr)\n", + "\n", + "ori_sr, con_sr = target_sr, target_sr" + ] + }, + { + "cell_type": "code", + "execution_count": 123, + "metadata": {}, + "outputs": [], + "source": [ + "def detect_onset_of_audio(audio, sample_rate):\n", + " onsets = librosa.onset.onset_detect(\n", + " y=audio, sr=sample_rate, units='samples', delta=0.3)\n", + " return onsets\n" + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "onsets = detect_onset_of_audio(ori_audio, ori_sr)\n", + "plt.figure(dpi=100)\n", + "\n", + "time = np.arange(ori_audio.shape[0])\n", + "plt.plot(time, ori_audio)\n", + "plt.vlines(onsets, 0, ymax=0.5, colors='r')\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Method\n", + "The baseline is quite simple, and it has several steps:\n", + "- Take the original waveform (encoded and decoded by our codebook) and detect the onsets to determine the timestamp of sound events\n", + "- (Optional) Assume we don't have original waveform, we can use Andrew's great hit model to predict sound from frames and detect onsets from it.\n", + "- Detect onsets of conditional waveform (encoded and decoded by our codebook) and clip single onset event from them as sound candicates\n", + "- For each onset of original waveform, replace with conditional onset event randomly and then generate sound" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "metadata": {}, + "outputs": [], + "source": [ + "def get_onset_audio_range(audio, onsets, i):\n", + " if i == 0:\n", + " prev_offset = int(onsets[i] // 3)\n", + " else:\n", + " prev_offset = int((onsets[i] - onsets[i - 1]) // 3)\n", + "\n", + " if i == onsets.shape[0] - 1:\n", + " post_offset = int((audio.shape[0] - onsets[i]) // 4 * 2)\n", + " else:\n", + " post_offset = int((onsets[i + 1] - onsets[i]) // 4 * 2)\n", + " return prev_offset, post_offset\n" + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "metadata": {}, + "outputs": [], + "source": [ + "ori_onsets = detect_onset_of_audio(ori_audio, ori_sr)\n", + "con_onsets = detect_onset_of_audio(con_audio, con_sr)\n", + "\n", + "np.random.seed(2022)\n", + "gen_audio = np.zeros_like(ori_audio)\n", + "for i in range(ori_onsets.shape[0]):\n", + " prev_offset, post_offset = get_onset_audio_range(ori_audio, ori_onsets, i)\n", + " j = np.random.choice(con_onsets.shape[0])\n", + " prev_offset_con, post_offset_con = get_onset_audio_range(con_audio, con_onsets, j)\n", + " prev_offset = min(prev_offset, prev_offset_con)\n", + " post_offset = min(post_offset, post_offset_con)\n", + " gen_audio[ori_onsets[i] - prev_offset: ori_onsets[i] + post_offset] = con_audio[con_onsets[j] - prev_offset: con_onsets[j] + post_offset]\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 127, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "plt.figure(dpi=100)\n", + "time = np.arange(gen_audio.shape[0])\n", + "plt.plot(time, gen_audio)\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 128, + "metadata": {}, + "outputs": [], + "source": [ + "# save audio\n", + "import soundfile as sf\n", + "sf.write('data/gen_audio.wav', gen_audio, ori_sr)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 129, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "t: 0%| | 0/49 [00:00\n", + " Your browser does not support the video element.\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 130, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Video('data/generate.mp4', width=640)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "interpreter": { + "hash": "ce61937b7f7dfb4402f1892711bcd3e4a6b6f6d238d7280e2db39bcb9fe9525c" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/foleycrafter/models/specvqgan/onset_baseline/demo_with_video_onset.ipynb b/foleycrafter/models/specvqgan/onset_baseline/demo_with_video_onset.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..36bdaab9a187a10e617c6c614d1dc03650c1caf2 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/demo_with_video_onset.ipynb @@ -0,0 +1,548 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Change audio by detecting onset \n", + "This notebook contains a method that could change the target video sound with a given audio." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load packages" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "import IPython\n", + "import os\n", + "import numpy as np\n", + "from moviepy.editor import *\n", + "import librosa\n", + "from IPython.display import Audio\n", + "from IPython.display import Video" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "# Read videos\n", + "origin_video_path = 'demo-data/original.mp4'\n", + "# conditional_video_path = 'demo-data/conditional.mp4'\n", + "conditional_video_path = 'demo-data/dog_bark.mp4'\n", + "\n", + "ori_videoclip = VideoFileClip(origin_video_path)\n", + "con_videoclip = VideoFileClip(conditional_video_path)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Video(origin_video_path, width=640)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Video(conditional_video_path, width=640)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "# get the audio track from video\n", + "ori_audioclip = ori_videoclip.audio\n", + "ori_audio, ori_sr = ori_audioclip.to_soundarray(), ori_audioclip.fps\n", + "con_audioclip = con_videoclip.audio\n", + "con_audio, con_sr = con_audioclip.to_soundarray(), con_audioclip.fps\n", + "\n", + "ori_audio = ori_audio.mean(-1)\n", + "con_audio = con_audio.mean(-1)\n", + "\n", + "target_sr = 22050\n", + "ori_audio = librosa.resample(ori_audio, orig_sr=ori_sr, target_sr=target_sr)\n", + "con_audio = librosa.resample(con_audio, orig_sr=con_sr, target_sr=target_sr)\n", + "\n", + "ori_sr, con_sr = target_sr, target_sr" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "def detect_onset_of_audio(audio, sample_rate):\n", + " onsets = librosa.onset.onset_detect(\n", + " y=audio, sr=sample_rate, units='samples', delta=0.3)\n", + " return onsets\n" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "onsets = detect_onset_of_audio(ori_audio, ori_sr)\n", + "plt.figure(dpi=100)\n", + "\n", + "time = np.arange(ori_audio.shape[0])\n", + "plt.plot(time, ori_audio)\n", + "plt.vlines(onsets, 0, ymax=0.8, colors='r')\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Method\n", + "The baseline is quite simple, and it has several steps:\n", + "- Take the original video, and apply self-trained video onset detection model to detect the onset\n", + "- Detect onsets of conditional waveform (encoded and decoded by our codebook) and clip single onset event from them as sound candicates\n", + "- For each onset of original waveform, replace with conditional onset event randomly and then generate sound" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "env: CUDA_VISIBLE_DEVICES=9\n", + "=> loading checkpoint 'checkpoints/EXP1/checkpoint_ep70.pth.tar'\n", + "=> loaded checkpoint 'checkpoints/EXP1/checkpoint_ep70.pth.tar' (epoch 70)\n" + ] + } + ], + "source": [ + "%env CUDA_VISIBLE_DEVICES=9\n", + "import argparse\n", + "import numpy as np\n", + "import os\n", + "import sys\n", + "import time\n", + "from tqdm import tqdm\n", + "from collections import OrderedDict\n", + "\n", + "import torch\n", + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", + "from torch.utils.data import DataLoader\n", + "from torch.utils.tensorboard import SummaryWriter\n", + "\n", + "\n", + "from config import init_args\n", + "import data\n", + "import models\n", + "from models import *\n", + "from utils import utils, torch_utils\n", + "\n", + "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", + "\n", + "\n", + "net = models.VideoOnsetNet(pretrained=False).to(device)\n", + "resume = 'checkpoints/EXP1/checkpoint_ep70.pth.tar'\n", + "net, _ = torch_utils.load_model(resume, net, device=device, strict=True)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [], + "source": [ + "import torchvision.transforms as transforms\n", + "from PIL import Image\n", + "\n", + "\n", + "vision_transform_list = [\n", + " transforms.Resize((128, 128)),\n", + " transforms.CenterCrop((112, 112)),\n", + " transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n", + "]\n", + "video_transform = transforms.Compose(vision_transform_list)\n", + "\n", + "def read_image(frame_list):\n", + " imgs = []\n", + " convert_tensor = transforms.ToTensor()\n", + " for img_path in frame_list:\n", + " image = Image.open(img_path).convert('RGB')\n", + " image = convert_tensor(image)\n", + " imgs.append(image.unsqueeze(0))\n", + " # (T, C, H ,W)\n", + " imgs = torch.cat(imgs, dim=0).squeeze()\n", + " imgs = video_transform(imgs)\n", + " imgs = imgs.permute(1, 0, 2, 3)\n", + " # (C, T, H ,W)\n", + " return imgs\n" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "# process videos into frames and read them\n", + "import glob\n", + "\n", + "save_path = 'demo-data/original_frames'\n", + "if os.path.exists(save_path):\n", + " os.system(f'rm -rf {save_path}')\n", + "os.makedirs(save_path)\n", + "command = f'ffmpeg -v quiet -y -i \\\"{origin_video_path}\\\" -f image2 -vf \\\"scale=-1:360,fps=15\\\" -qscale:v 3 \\\"{save_path}\\\"/frame%06d.jpg'\n", + "os.system(command)\n", + "\n", + "frame_list = glob.glob(f'{save_path}/*.jpg')\n", + "frame_list.sort()\n", + "frame_list = frame_list[:2 * 15]\n", + "\n", + "frames = read_image(frame_list)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "inputs = {\n", + " 'frames': frames.unsqueeze(0).to(device)\n", + "}\n", + "pred = net(inputs).squeeze()\n", + "pred = torch.sigmoid(pred).data.cpu().numpy()" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "def postprocess_video_onsets(probs, thres=0.5, nearest=5):\n", + " # import pdb; pdb.set_trace()\n", + " video_onsets = []\n", + " pred = np.array(probs, copy=True)\n", + " while True:\n", + " max_ind = np.argmax(pred)\n", + " video_onsets.append(max_ind)\n", + " low = max(max_ind - nearest, 0)\n", + " high = min(max_ind + nearest, pred.shape[0])\n", + " pred[low: high] = 0\n", + " if (pred > thres).sum() == 0:\n", + " break\n", + " video_onsets.sort()\n", + " video_onsets = np.array(video_onsets)\n", + " return video_onsets\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "# video_onsets = (np.nonzero(pred > 0.5)[0] / 15 * ori_sr).astype(int)\n", + "video_onsets = postprocess_video_onsets(pred, thres=0.5, nearest=4)\n", + "video_onsets = (video_onsets / 15 * ori_sr).astype(int)\n", + "plt.figure(dpi=100)\n", + "\n", + "time = np.arange(ori_audio.shape[0])\n", + "plt.plot(time, ori_audio)\n", + "plt.vlines(video_onsets, 0, ymax=0.8, colors='r')\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-0.06068027, -0.0599093 , -0.05623583, -0.01206349])" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(onsets - video_onsets) / ori_sr\n", + "# video_onsets" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "def get_onset_audio_range(audio_len, onsets, i):\n", + " if i == 0:\n", + " prev_offset = int(onsets[i] // 3)\n", + " else:\n", + " prev_offset = int((onsets[i] - onsets[i - 1]) // 3)\n", + "\n", + " if i == onsets.shape[0] - 1:\n", + " post_offset = int((audio_len - onsets[i]) // 4 * 2)\n", + " else:\n", + " post_offset = int((onsets[i + 1] - onsets[i]) // 4 * 2)\n", + " return prev_offset, post_offset\n" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [], + "source": [ + "# ori_onsets = detect_onset_of_audio(ori_audio, ori_sr)\n", + "con_onsets = detect_onset_of_audio(con_audio, con_sr)\n", + "\n", + "np.random.seed(2022)\n", + "gen_audio = np.zeros_like(ori_audio)\n", + "for i in range(video_onsets.shape[0]):\n", + " prev_offset, post_offset = get_onset_audio_range(int(con_sr * 2), video_onsets, i)\n", + " j = np.random.choice(con_onsets.shape[0])\n", + " prev_offset_con, post_offset_con = get_onset_audio_range(con_audio.shape[0], con_onsets, j)\n", + " prev_offset = min(prev_offset, prev_offset_con)\n", + " post_offset = min(post_offset, post_offset_con)\n", + " gen_audio[video_onsets[i] - prev_offset: video_onsets[i] + post_offset] = con_audio[con_onsets[j] - prev_offset: con_onsets[j] + post_offset]\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "plt.figure(dpi=100)\n", + "time = np.arange(gen_audio.shape[0])\n", + "plt.plot(time, gen_audio)\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [], + "source": [ + "# save audio\n", + "import soundfile as sf\n", + "sf.write('data/gen_audio.wav', gen_audio, ori_sr)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "t: 58%|█████▊ | 26/45 [00:41<00:05, 3.45it/s, now=None]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Moviepy - Building video data/generate.mp4.\n", + "MoviePy - Writing audio in generateTEMP_MPY_wvf_snd.mp3\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "t: 58%|█████▊ | 26/45 [00:42<00:05, 3.45it/s, now=None]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MoviePy - Done.\n", + "Moviepy - Writing video data/generate.mp4\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "t: 58%|█████▊ | 26/45 [01:03<00:05, 3.45it/s, now=None]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Moviepy - Done !\n", + "Moviepy - video ready data/generate.mp4\n" + ] + } + ], + "source": [ + "gen_audioclip = AudioFileClip(\"data/gen_audio.wav\")\n", + "gen_videoclip = ori_videoclip.set_audio(gen_audioclip)\n", + "gen_videoclip.write_videofile('data/generate.mp4')\n" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Video('data/generate.mp4', width=640)\n" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "419ed25a44e8f5205333d07bc5a26d3abb4bd07afa4dac02924f75b129c3e2d9" + }, + "kernelspec": { + "display_name": "Python 3.8.8 ('AVanalogy')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/foleycrafter/models/specvqgan/onset_baseline/main.py b/foleycrafter/models/specvqgan/onset_baseline/main.py new file mode 100644 index 0000000000000000000000000000000000000000..be1b7968118f37a6663fa01a471be74ab905ff86 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/main.py @@ -0,0 +1,202 @@ +import argparse +import numpy as np +import os +import sys +import time +from tqdm import tqdm +from collections import OrderedDict + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.utils.data import DataLoader +from torch.utils.tensorboard import SummaryWriter + +from config import init_args +import data +import models +from models import * +from utils import utils, torch_utils + + +DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") + +def validation(args, net, criterion, data_loader, device='cuda'): + # import pdb; pdb.set_trace() + net.eval() + pred_all = torch.tensor([]).to(device) + target_all = torch.tensor([]).to(device) + with torch.no_grad(): + for step, batch in tqdm(enumerate(data_loader), total=len(data_loader), desc="Validation"): + pred, target = predict(args, net, batch, device) + pred_all = torch.cat([pred_all, pred], dim=0) + target_all = torch.cat([target_all, target], dim=0) + + res = criterion.evaluate(pred_all, target_all) + torch.cuda.empty_cache() + net.train() + return res + + +def predict(args, net, batch, device): + inputs = { + 'frames': batch['frames'].to(device) + } + pred = net(inputs) + target = batch['label'].to(device) + return pred, target + + +def train(args, device): + # save dir + gpus = torch.cuda.device_count() + gpu_ids = list(range(gpus)) + + # ----- make dirs for checkpoints ----- # + sys.stdout = utils.LoggerOutput(os.path.join('checkpoints', args.exp, 'log.txt')) + os.makedirs('./checkpoints/' + args.exp, exist_ok=True) + + writer = SummaryWriter(os.path.join('./checkpoints', args.exp, 'visualization')) + # ------------------------------------- # + tqdm.write('{}'.format(args)) + + # ------------------------------------ # + + + # ----- Dataset and Dataloader ----- # + train_dataset = data.GreatestHitDataset(args, split='train') + # train_dataset.getitem_test(1) + train_loader = DataLoader( + train_dataset, + batch_size=args.batch_size, + shuffle=True, + num_workers=args.num_workers, + pin_memory=True, + drop_last=False) + + val_dataset = data.GreatestHitDataset(args, split='val') + val_loader = DataLoader( + val_dataset, + batch_size=args.batch_size, + shuffle=False, + num_workers=args.num_workers, + pin_memory=True, + drop_last=False) + # --------------------------------- # + + # ----- Network ----- # + net = models.VideoOnsetNet(pretrained=False).to(device) + criterion = models.BCLoss(args) + optimizer = torch_utils.make_optimizer(net, args) + # --------------------- # + + # -------- Loading checkpoints weights ------------- # + if args.resume: + resume = './checkpoints/' + args.resume + net, args.start_epoch = torch_utils.load_model(resume, net, device=device, strict=True) + if args.resume_optim: + tqdm.write('loading optimizer...') + optim_state = torch.load(resume)['optimizer'] + optimizer.load_state_dict(optim_state) + tqdm.write('loaded optimizer!') + else: + args.start_epoch = 0 + + # ------------------- + net = nn.DataParallel(net, device_ids=gpu_ids) + # --------- Random or resume validation ------------ # + res = validation(args, net, criterion, val_loader, device) + writer.add_scalars('VideoOnset' + '/validation', res, args.start_epoch) + tqdm.write("Beginning, Validation results: {}".format(res)) + tqdm.write('\n') + + # ----------------- Training ---------------- # + # import pdb; pdb.set_trace() + VALID_STEP = args.valid_step + for epoch in range(args.start_epoch, args.epochs): + running_loss = 0.0 + torch_utils.adjust_learning_rate(optimizer, epoch, args) + for step, batch in tqdm(enumerate(train_loader), total=len(train_loader), desc="Training"): + pred, target = predict(args, net, batch, device) + loss = criterion(pred, target) + optimizer.zero_grad() + loss.backward() + optimizer.step() + + if step % 1 == 0: + tqdm.write("Epoch: {}/{}, step: {}/{}, loss: {}".format(epoch+1, args.epochs, step+1, len(train_loader), loss)) + running_loss += loss.item() + + current_step = epoch * len(train_loader) + step + 1 + BOARD_STEP = 3 + if (step+1) % BOARD_STEP == 0: + writer.add_scalar('VideoOnset' + '/training loss', running_loss / BOARD_STEP, current_step) + running_loss = 0.0 + + + # ----------- Validtion -------------- # + if (epoch + 1) % VALID_STEP == 0: + res = validation(args, net, criterion, val_loader, device) + writer.add_scalars('VideoOnset' + '/validation', res, epoch + 1) + tqdm.write("Epoch: {}/{}, Validation results: {}".format(epoch + 1, args.epochs, res)) + + # ---------- Save model ----------- # + SAVE_STEP = args.save_step + if (epoch + 1) % SAVE_STEP == 0: + path = os.path.join('./checkpoints', args.exp, 'checkpoint_ep' + str(epoch + 1) + '.pth.tar') + torch.save({'epoch': epoch + 1, + 'step': current_step, + 'state_dict': net.state_dict(), + 'optimizer': optimizer.state_dict(), + }, + path) + # --------------------------------- # + torch.cuda.empty_cache() + tqdm.write('Training Complete!') + writer.close() + + +def test(args, device): + # save dir + gpus = torch.cuda.device_count() + gpu_ids = list(range(gpus)) + + # ----- make dirs for results ----- # + sys.stdout = utils.LoggerOutput(os.path.join('results', args.exp, 'log.txt')) + os.makedirs('./results/' + args.exp, exist_ok=True) + # ------------------------------------- # + tqdm.write('{}'.format(args)) + # ------------------------------------ # + # ----- Dataset and Dataloader ----- # + test_dataset = data.GreatestHitDataset(args, split='test') + test_loader = DataLoader( + test_dataset, + batch_size=args.batch_size, + shuffle=False, + num_workers=args.num_workers, + pin_memory=True, + drop_last=False) + + # --------------------------------- # + # ----- Network ----- # + net = models.VideoOnsetNet(pretrained=False).to(device) + criterion = models.BCLoss(args) + # -------- Loading checkpoints weights ------------- # + if args.resume: + resume = './checkpoints/' + args.resume + net, _ = torch_utils.load_model(resume, net, device=device, strict=True) + + # ------------------- # + net = nn.DataParallel(net, device_ids=gpu_ids) + # --------- Testing ------------ # + res = validation(args, net, criterion, test_loader, device) + tqdm.write("Testing results: {}".format(res)) + + +# CUDA_VISIBLE_DEVICES=1 python main.py --exp='EXP1' --epochs=100 --batch_size=12 --num_workers=8 --save_step=10 --valid_step=1 --lr=0.0001 --optim='Adam' --repeat=1 --schedule='cos' +if __name__ == '__main__': + args = init_args() + if args.test_mode: + test(args, DEVICE) + else: + train(args, DEVICE) \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/main_cxav.py b/foleycrafter/models/specvqgan/onset_baseline/main_cxav.py new file mode 100644 index 0000000000000000000000000000000000000000..498ce1fd3cddb79d0e175501ed43c009fe9aa098 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/main_cxav.py @@ -0,0 +1,202 @@ +import argparse +import numpy as np +import os +import sys +import time +from tqdm import tqdm +from collections import OrderedDict + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.utils.data import DataLoader +from torch.utils.tensorboard import SummaryWriter + +from config import init_args +import data +import models +from models import * +from utils import utils, torch_utils + + +DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") + +def validation(args, net, criterion, data_loader, device='cuda'): + # import pdb; pdb.set_trace() + net.eval() + pred_all = torch.tensor([]).to(device) + target_all = torch.tensor([]).to(device) + with torch.no_grad(): + for step, batch in tqdm(enumerate(data_loader), total=len(data_loader), desc="Validation"): + pred, target = predict(args, net, batch, device) + pred_all = torch.cat([pred_all, pred], dim=0) + target_all = torch.cat([target_all, target], dim=0) + + res = criterion.evaluate(pred_all, target_all) + torch.cuda.empty_cache() + net.train() + return res + + +def predict(args, net, batch, device): + inputs = { + 'frames': batch['frames'].to(device) + } + pred = net(inputs) + target = batch['label'].to(device) + return pred, target + + +def train(args, device): + # save dir + gpus = torch.cuda.device_count() + gpu_ids = list(range(gpus)) + + # ----- make dirs for checkpoints ----- # + sys.stdout = utils.LoggerOutput(os.path.join('checkpoints', args.exp, 'log.txt')) + os.makedirs('./checkpoints/' + args.exp, exist_ok=True) + + writer = SummaryWriter(os.path.join('./checkpoints', args.exp, 'visualization')) + # ------------------------------------- # + tqdm.write('{}'.format(args)) + + # ------------------------------------ # + + + # ----- Dataset and Dataloader ----- # + train_dataset = data.CountixAVDataset(args, split='train') + # train_dataset.getitem_test(1) + train_loader = DataLoader( + train_dataset, + batch_size=args.batch_size, + shuffle=True, + num_workers=args.num_workers, + pin_memory=True, + drop_last=False) + + val_dataset = data.CountixAVDataset(args, split='val') + val_loader = DataLoader( + val_dataset, + batch_size=args.batch_size, + shuffle=False, + num_workers=args.num_workers, + pin_memory=True, + drop_last=False) + # --------------------------------- # + + # ----- Network ----- # + net = models.VideoOnsetNet(pretrained=False).to(device) + criterion = models.BCLoss(args) + optimizer = torch_utils.make_optimizer(net, args) + # --------------------- # + + # -------- Loading checkpoints weights ------------- # + if args.resume: + resume = './checkpoints/' + args.resume + net, args.start_epoch = torch_utils.load_model(resume, net, device=device, strict=True) + if args.resume_optim: + tqdm.write('loading optimizer...') + optim_state = torch.load(resume)['optimizer'] + optimizer.load_state_dict(optim_state) + tqdm.write('loaded optimizer!') + else: + args.start_epoch = 0 + + # ------------------- + net = nn.DataParallel(net, device_ids=gpu_ids) + # --------- Random or resume validation ------------ # + res = validation(args, net, criterion, val_loader, device) + writer.add_scalars('VideoOnset' + '/validation', res, args.start_epoch) + tqdm.write("Beginning, Validation results: {}".format(res)) + tqdm.write('\n') + + # ----------------- Training ---------------- # + # import pdb; pdb.set_trace() + VALID_STEP = args.valid_step + for epoch in range(args.start_epoch, args.epochs): + running_loss = 0.0 + torch_utils.adjust_learning_rate(optimizer, epoch, args) + for step, batch in tqdm(enumerate(train_loader), total=len(train_loader), desc="Training"): + pred, target = predict(args, net, batch, device) + loss = criterion(pred, target) + optimizer.zero_grad() + loss.backward() + optimizer.step() + + if step % 1 == 0: + tqdm.write("Epoch: {}/{}, step: {}/{}, loss: {}".format(epoch+1, args.epochs, step+1, len(train_loader), loss)) + running_loss += loss.item() + + current_step = epoch * len(train_loader) + step + 1 + BOARD_STEP = 3 + if (step+1) % BOARD_STEP == 0: + writer.add_scalar('VideoOnset' + '/training loss', running_loss / BOARD_STEP, current_step) + running_loss = 0.0 + + + # ----------- Validtion -------------- # + if (epoch + 1) % VALID_STEP == 0: + res = validation(args, net, criterion, val_loader, device) + writer.add_scalars('VideoOnset' + '/validation', res, epoch + 1) + tqdm.write("Epoch: {}/{}, Validation results: {}".format(epoch + 1, args.epochs, res)) + + # ---------- Save model ----------- # + SAVE_STEP = args.save_step + if (epoch + 1) % SAVE_STEP == 0: + path = os.path.join('./checkpoints', args.exp, 'checkpoint_ep' + str(epoch + 1) + '.pth.tar') + torch.save({'epoch': epoch + 1, + 'step': current_step, + 'state_dict': net.state_dict(), + 'optimizer': optimizer.state_dict(), + }, + path) + # --------------------------------- # + torch.cuda.empty_cache() + tqdm.write('Training Complete!') + writer.close() + + +def test(args, device): + # save dir + gpus = torch.cuda.device_count() + gpu_ids = list(range(gpus)) + + # ----- make dirs for results ----- # + sys.stdout = utils.LoggerOutput(os.path.join('results', args.exp, 'log.txt')) + os.makedirs('./results/' + args.exp, exist_ok=True) + # ------------------------------------- # + tqdm.write('{}'.format(args)) + # ------------------------------------ # + # ----- Dataset and Dataloader ----- # + test_dataset = data.CountixAVDataset(args, split='test') + test_loader = DataLoader( + test_dataset, + batch_size=args.batch_size, + shuffle=False, + num_workers=args.num_workers, + pin_memory=True, + drop_last=False) + + # --------------------------------- # + # ----- Network ----- # + net = models.VideoOnsetNet(pretrained=False).to(device) + criterion = models.BCLoss(args) + # -------- Loading checkpoints weights ------------- # + if args.resume: + resume = './checkpoints/' + args.resume + net, _ = torch_utils.load_model(resume, net, device=device, strict=True) + + # ------------------- # + net = nn.DataParallel(net, device_ids=gpu_ids) + # --------- Testing ------------ # + res = validation(args, net, criterion, test_loader, device) + tqdm.write("Testing results: {}".format(res)) + + +# CUDA_VISIBLE_DEVICES=1 python main.py --exp='EXP1' --epochs=100 --batch_size=12 --num_workers=8 --save_step=10 --valid_step=1 --lr=0.0001 --optim='Adam' --repeat=1 --schedule='cos' +if __name__ == '__main__': + args = init_args() + if args.test_mode: + test(args, DEVICE) + else: + train(args, DEVICE) \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/models/__init__.py b/foleycrafter/models/specvqgan/onset_baseline/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b314242ca0d707d9e6f4a39937fbe119eaf88c62 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/models/__init__.py @@ -0,0 +1,3 @@ +from .resnet import * +from .r2plus1d_18 import * +from .video_onset_net import * \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/models/r2plus1d_18.py b/foleycrafter/models/specvqgan/onset_baseline/models/r2plus1d_18.py new file mode 100644 index 0000000000000000000000000000000000000000..4c2d3a4de4ff8d1166100ddc47f14d09ab1119b3 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/models/r2plus1d_18.py @@ -0,0 +1,51 @@ +import torch +import torch.nn as nn + + +import sys +sys.path.append('..') +from foleycrafter.models.specvqgan.onset_baseline.models.resnet import r2plus1d_18 + + +class r2plus1d18KeepTemp(nn.Module): + + def __init__(self, pretrained=True): + super().__init__() + + self.model = r2plus1d_18(pretrained=pretrained) + + self.model.layer2[0].conv1[0][3] = nn.Conv3d(230, 128, kernel_size=(3, 1, 1), + stride=(1, 1, 1), padding=(1, 0, 0), bias=False) + self.model.layer2[0].downsample = nn.Sequential( + nn.Conv3d(64, 128, kernel_size=(1, 1, 1), stride=(1, 2, 2), bias=False), + nn.BatchNorm3d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) + ) + self.model.layer3[0].conv1[0][3] = nn.Conv3d(460, 256, kernel_size=(3, 1, 1), + stride=(1, 1, 1), padding=(1, 0, 0), bias=False) + self.model.layer3[0].downsample = nn.Sequential( + nn.Conv3d(128, 256, kernel_size=(1, 1, 1), stride=(1, 2, 2), bias=False), + nn.BatchNorm3d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) + ) + self.model.layer4[0].conv1[0][3] = nn.Conv3d(921, 512, kernel_size=(3, 1, 1), + stride=(1, 1, 1), padding=(1, 0, 0), bias=False) + self.model.layer4[0].downsample = nn.Sequential( + nn.Conv3d(256, 512, kernel_size=(1, 1, 1), stride=(1, 2, 2), bias=False), + nn.BatchNorm3d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) + ) + self.model.avgpool = nn.AdaptiveAvgPool3d((None, 1, 1)) + self.model.fc = nn.Identity() + + + def forward(self, x): + # import pdb; pdb.set_trace() + x = self.model(x) + return x + + + + +if __name__ == '__main__': + model = r2plus1d18KeepTemp(False).cuda() + rand_input = torch.randn((1, 3, 30, 112, 112)).cuda() + out = model(rand_input) + diff --git a/foleycrafter/models/specvqgan/onset_baseline/models/resnet.py b/foleycrafter/models/specvqgan/onset_baseline/models/resnet.py new file mode 100644 index 0000000000000000000000000000000000000000..5bc15653409a60c61a4d053ee9a69dc4be119e65 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/models/resnet.py @@ -0,0 +1,348 @@ +import torch.nn as nn + +# from torchvision.models.utils import load_state_dict_from_url +from torch.hub import load_state_dict_from_url + + +__all__ = ['r3d_18', 'mc3_18', 'r2plus1d_18'] + +model_urls = { + 'r3d_18': 'https://download.pytorch.org/models/r3d_18-b3b3357e.pth', + 'mc3_18': 'https://download.pytorch.org/models/mc3_18-a90a0ba3.pth', + 'r2plus1d_18': 'https://download.pytorch.org/models/r2plus1d_18-91a641e6.pth', +} + + +class Conv3DSimple(nn.Conv3d): + def __init__(self, + in_planes, + out_planes, + midplanes=None, + stride=1, + padding=1): + + super(Conv3DSimple, self).__init__( + in_channels=in_planes, + out_channels=out_planes, + kernel_size=(3, 3, 3), + stride=stride, + padding=padding, + bias=False) + + @staticmethod + def get_downsample_stride(stride): + return stride, stride, stride + + +class Conv2Plus1D(nn.Sequential): + + def __init__(self, + in_planes, + out_planes, + midplanes, + stride=1, + padding=1): + super(Conv2Plus1D, self).__init__( + nn.Conv3d(in_planes, midplanes, kernel_size=(1, 3, 3), + stride=(1, stride, stride), padding=(0, padding, padding), + bias=False), + nn.BatchNorm3d(midplanes), + nn.ReLU(inplace=True), + nn.Conv3d(midplanes, out_planes, kernel_size=(3, 1, 1), + stride=(stride, 1, 1), padding=(padding, 0, 0), + bias=False)) + + @staticmethod + def get_downsample_stride(stride): + return stride, stride, stride + + +class Conv3DNoTemporal(nn.Conv3d): + + def __init__(self, + in_planes, + out_planes, + midplanes=None, + stride=1, + padding=1): + + super(Conv3DNoTemporal, self).__init__( + in_channels=in_planes, + out_channels=out_planes, + kernel_size=(1, 3, 3), + stride=(1, stride, stride), + padding=(0, padding, padding), + bias=False) + + @staticmethod + def get_downsample_stride(stride): + return 1, stride, stride + + +class BasicBlock(nn.Module): + + expansion = 1 + + def __init__(self, inplanes, planes, conv_builder, stride=1, downsample=None): + midplanes = (inplanes * planes * 3 * 3 * + 3) // (inplanes * 3 * 3 + 3 * planes) + + super(BasicBlock, self).__init__() + self.conv1 = nn.Sequential( + conv_builder(inplanes, planes, midplanes, stride), + nn.BatchNorm3d(planes), + nn.ReLU(inplace=True) + ) + self.conv2 = nn.Sequential( + conv_builder(planes, planes, midplanes), + nn.BatchNorm3d(planes) + ) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.conv2(out) + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, conv_builder, stride=1, downsample=None): + + super(Bottleneck, self).__init__() + midplanes = (inplanes * planes * 3 * 3 * + 3) // (inplanes * 3 * 3 + 3 * planes) + + # 1x1x1 + self.conv1 = nn.Sequential( + nn.Conv3d(inplanes, planes, kernel_size=1, bias=False), + nn.BatchNorm3d(planes), + nn.ReLU(inplace=True) + ) + # Second kernel + self.conv2 = nn.Sequential( + conv_builder(planes, planes, midplanes, stride), + nn.BatchNorm3d(planes), + nn.ReLU(inplace=True) + ) + + # 1x1x1 + self.conv3 = nn.Sequential( + nn.Conv3d(planes, planes * self.expansion, + kernel_size=1, bias=False), + nn.BatchNorm3d(planes * self.expansion) + ) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.conv2(out) + out = self.conv3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class BasicStem(nn.Sequential): + """The default conv-batchnorm-relu stem + """ + + def __init__(self): + super(BasicStem, self).__init__( + nn.Conv3d(3, 64, kernel_size=(3, 7, 7), stride=(1, 2, 2), + padding=(1, 3, 3), bias=False), + nn.BatchNorm3d(64), + nn.ReLU(inplace=True)) + + +class R2Plus1dStem(nn.Sequential): + """R(2+1)D stem is different than the default one as it uses separated 3D convolution + """ + + def __init__(self): + super(R2Plus1dStem, self).__init__( + nn.Conv3d(3, 45, kernel_size=(1, 7, 7), + stride=(1, 2, 2), padding=(0, 3, 3), + bias=False), + nn.BatchNorm3d(45), + nn.ReLU(inplace=True), + nn.Conv3d(45, 64, kernel_size=(3, 1, 1), + stride=(1, 1, 1), padding=(1, 0, 0), + bias=False), + nn.BatchNorm3d(64), + nn.ReLU(inplace=True)) + + +class VideoResNet(nn.Module): + + def __init__(self, block, conv_makers, layers, + stem, num_classes=400, + zero_init_residual=False): + """Generic resnet video generator. + Args: + block (nn.Module): resnet building block + conv_makers (list(functions)): generator function for each layer + layers (List[int]): number of blocks per layer + stem (nn.Module, optional): Resnet stem, if None, defaults to conv-bn-relu. Defaults to None. + num_classes (int, optional): Dimension of the final FC layer. Defaults to 400. + zero_init_residual (bool, optional): Zero init bottleneck residual BN. Defaults to False. + """ + super(VideoResNet, self).__init__() + self.inplanes = 64 + + self.stem = stem() + + self.layer1 = self._make_layer( + block, conv_makers[0], 64, layers[0], stride=1) + self.layer2 = self._make_layer( + block, conv_makers[1], 128, layers[1], stride=2) + self.layer3 = self._make_layer( + block, conv_makers[2], 256, layers[2], stride=2) + self.layer4 = self._make_layer( + block, conv_makers[3], 512, layers[3], stride=2) + + self.avgpool = nn.AdaptiveAvgPool3d((1, 1, 1)) + self.fc = nn.Linear(512 * block.expansion, num_classes) + + # init weights + self._initialize_weights() + + if zero_init_residual: + for m in self.modules(): + if isinstance(m, Bottleneck): + nn.init.constant_(m.bn3.weight, 0) + + def forward(self, x): + x = self.stem(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + # Flatten the layer to fc + # x = x.flatten(1) + # x = self.fc(x) + N = x.shape[0] + x = x.squeeze() + if N == 1: + x = x[None] + + return x + + def _make_layer(self, block, conv_builder, planes, blocks, stride=1): + downsample = None + + if stride != 1 or self.inplanes != planes * block.expansion: + ds_stride = conv_builder.get_downsample_stride(stride) + downsample = nn.Sequential( + nn.Conv3d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=ds_stride, bias=False), + nn.BatchNorm3d(planes * block.expansion) + ) + layers = [] + layers.append(block(self.inplanes, planes, + conv_builder, stride, downsample)) + + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes, conv_builder)) + + return nn.Sequential(*layers) + + def _initialize_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv3d): + nn.init.kaiming_normal_(m.weight, mode='fan_out', + nonlinearity='relu') + if m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm3d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.constant_(m.bias, 0) + + +def _video_resnet(arch, pretrained=False, progress=True, **kwargs): + model = VideoResNet(**kwargs) + + if pretrained: + state_dict = load_state_dict_from_url(model_urls[arch], + progress=progress) + model.load_state_dict(state_dict) + return model + + +def r3d_18(pretrained=False, progress=True, **kwargs): + """Construct 18 layer Resnet3D model as in + https://arxiv.org/abs/1711.11248 + Args: + pretrained (bool): If True, returns a model pre-trained on Kinetics-400 + progress (bool): If True, displays a progress bar of the download to stderr + Returns: + nn.Module: R3D-18 network + """ + + return _video_resnet('r3d_18', + pretrained, progress, + block=BasicBlock, + conv_makers=[Conv3DSimple] * 4, + layers=[2, 2, 2, 2], + stem=BasicStem, **kwargs) + + +def mc3_18(pretrained=False, progress=True, **kwargs): + """Constructor for 18 layer Mixed Convolution network as in + https://arxiv.org/abs/1711.11248 + Args: + pretrained (bool): If True, returns a model pre-trained on Kinetics-400 + progress (bool): If True, displays a progress bar of the download to stderr + Returns: + nn.Module: MC3 Network definition + """ + return _video_resnet('mc3_18', + pretrained, progress, + block=BasicBlock, + conv_makers=[Conv3DSimple] + [Conv3DNoTemporal] * 3, + layers=[2, 2, 2, 2], + stem=BasicStem, **kwargs) + + +def r2plus1d_18(pretrained=False, progress=True, **kwargs): + """Constructor for the 18 layer deep R(2+1)D network as in + https://arxiv.org/abs/1711.11248 + Args: + pretrained (bool): If True, returns a model pre-trained on Kinetics-400 + progress (bool): If True, displays a progress bar of the download to stderr + Returns: + nn.Module: R(2+1)D-18 network + """ + return _video_resnet('r2plus1d_18', + pretrained, progress, + block=BasicBlock, + conv_makers=[Conv2Plus1D] * 4, + layers=[2, 2, 2, 2], + stem=R2Plus1dStem, **kwargs) diff --git a/foleycrafter/models/specvqgan/onset_baseline/models/video_onset_net.py b/foleycrafter/models/specvqgan/onset_baseline/models/video_onset_net.py new file mode 100644 index 0000000000000000000000000000000000000000..01fc395c1809c7234e47152328ca419c21575196 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/models/video_onset_net.py @@ -0,0 +1,78 @@ +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from sklearn.metrics import average_precision_score +import sys +sys.path.append('..') +from foleycrafter.models.specvqgan.onset_baseline.models import r2plus1d18KeepTemp +from foleycrafter.models.specvqgan.onset_baseline.utils import torch_utils + +class VideoOnsetNet(nn.Module): + # Video Onset detection network + def __init__(self, pretrained): + super(VideoOnsetNet, self).__init__() + self.net = r2plus1d18KeepTemp(pretrained=pretrained) + self.fc = nn.Sequential( + nn.Linear(512, 128), + nn.ReLU(True), + nn.Linear(128, 1) + ) + + def forward(self, inputs, loss=False, evaluate=False): + # import pdb; pdb.set_trace() + x = inputs['frames'] + x = self.net(x) + x = x.transpose(-1, -2) + x = self.fc(x) + x = x.squeeze(-1) + + return x + + +class BCLoss(nn.Module): + # binary classification loss + def __init__(self, args): + super(BCLoss, self).__init__() + + def forward(self, pred, target): + # import pdb; pdb.set_trace() + pred = pred.contiguous().view(-1) + target = target.contiguous().view(-1) + pos_weight = (target.shape[0] - target.sum()) / target.sum() + criterion = torch.nn.BCEWithLogitsLoss(pos_weight=pos_weight).to(pred.device) + loss = criterion(pred, target.float()) + return loss + + def evaluate(self, pred, target): + # import pdb; pdb.set_trace() + + pred = pred.contiguous().view(-1) + target = target.contiguous().view(-1) + pred = torch.sigmoid(pred) + pred = pred.data.cpu().numpy() + target = target.data.cpu().numpy() + + pos_index = np.nonzero(target == 1)[0] + neg_index = np.nonzero(target == 0)[0] + balance_num = min(pos_index.shape[0], neg_index.shape[0]) + index = np.concatenate((pos_index[:balance_num], neg_index[:balance_num]), axis=0) + pred = pred[index] + target = target[index] + ap = average_precision_score(target, pred) + acc = torch_utils.binary_acc(pred, target, thred=0.5) + res = { + 'AP': ap, + 'Acc': acc + } + return res + + + +if __name__ == '__main__': + model = VideoOnsetNet(False).cuda() + rand_input = torch.randn((1, 3, 30, 112, 112)).cuda() + inputs = { + 'frames': rand_input + } + out = model(inputs) \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/onset_gen.py b/foleycrafter/models/specvqgan/onset_baseline/onset_gen.py new file mode 100644 index 0000000000000000000000000000000000000000..3dbb12dad941a6e3c526bcea8575506e7bf071d5 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/onset_gen.py @@ -0,0 +1,189 @@ +import glob +import os +import numpy as np +from moviepy.editor import * +import librosa +import soundfile as sf + +import argparse +import numpy as np +import os +import sys +import time +from tqdm import tqdm +from collections import OrderedDict + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.utils.data import DataLoader +from torch.utils.tensorboard import SummaryWriter +import torchvision.transforms as transforms +from PIL import Image +import shutil + +from config import init_args +import data +import models +from models import * +from utils import utils, torch_utils + + +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + +vision_transform_list = [ + transforms.Resize((128, 128)), + transforms.CenterCrop((112, 112)), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) +] +video_transform = transforms.Compose(vision_transform_list) + +def read_image(frame_list): + imgs = [] + convert_tensor = transforms.ToTensor() + for img_path in frame_list: + image = Image.open(img_path).convert('RGB') + image = convert_tensor(image) + imgs.append(image.unsqueeze(0)) + # (T, C, H ,W) + imgs = torch.cat(imgs, dim=0).squeeze() + imgs = video_transform(imgs) + imgs = imgs.permute(1, 0, 2, 3) + # (C, T, H ,W) + return imgs + + +def get_video_frames(origin_video_path): + save_path = 'results/temp' + if os.path.exists(save_path): + os.system(f'rm -rf {save_path}') + os.makedirs(save_path) + command = f'ffmpeg -v quiet -y -i \"{origin_video_path}\" -f image2 -vf \"scale=-1:360,fps=15\" -qscale:v 3 \"{save_path}\"/frame%06d.jpg' + os.system(command) + frame_list = glob.glob(f'{save_path}/*.jpg') + frame_list.sort() + frame_list = frame_list[:2 * 15] + frames = read_image(frame_list) + return frames + + +def postprocess_video_onsets(probs, thres=0.5, nearest=5): + # import pdb; pdb.set_trace() + video_onsets = [] + pred = np.array(probs, copy=True) + while True: + max_ind = np.argmax(pred) + video_onsets.append(max_ind) + low = max(max_ind - nearest, 0) + high = min(max_ind + nearest, pred.shape[0]) + pred[low: high] = 0 + if (pred > thres).sum() == 0: + break + video_onsets.sort() + video_onsets = np.array(video_onsets) + return video_onsets + + +def detect_onset_of_audio(audio, sample_rate): + onsets = librosa.onset.onset_detect( + y=audio, sr=sample_rate, units='samples', delta=0.3) + return onsets + + +def get_onset_audio_range(audio_len, onsets, i): + if i == 0: + prev_offset = int(onsets[i] // 3) + else: + prev_offset = int((onsets[i] - onsets[i - 1]) // 3) + + if i == onsets.shape[0] - 1: + post_offset = int((audio_len - onsets[i]) // 4 * 2) + else: + post_offset = int((onsets[i + 1] - onsets[i]) // 4 * 2) + return prev_offset, post_offset + + +def generate_audio(con_videoclip, video_onsets): + np.random.seed(2022) + con_audioclip = con_videoclip.audio + con_audio, con_sr = con_audioclip.to_soundarray(), con_audioclip.fps + con_audio = con_audio.mean(-1) + target_sr = 22050 + if target_sr != con_sr: + con_audio = librosa.resample(con_audio, orig_sr=con_sr, target_sr=target_sr) + con_sr = target_sr + + con_onsets = detect_onset_of_audio(con_audio, con_sr) + gen_audio = np.zeros(int(2 * con_sr)) + + for i in range(video_onsets.shape[0]): + prev_offset, post_offset = get_onset_audio_range( + int(con_sr * 2), video_onsets, i) + j = np.random.choice(con_onsets.shape[0]) + prev_offset_con, post_offset_con = get_onset_audio_range( + con_audio.shape[0], con_onsets, j) + prev_offset = min(prev_offset, prev_offset_con) + post_offset = min(post_offset, post_offset_con) + gen_audio[video_onsets[i] - prev_offset: video_onsets[i] + + post_offset] = con_audio[con_onsets[j] - prev_offset: con_onsets[j] + post_offset] + return gen_audio + + +def generate_video(net, original_video_list, cond_video_list_0, cond_video_list_1, cond_video_list_2): + save_folder = 'results/onset_baseline/vis' + os.makedirs(save_folder, exist_ok=True) + origin_video_folder = os.path.join(save_folder, '0_original') + os.makedirs(origin_video_folder, exist_ok=True) + + for i in range(len(original_video_list)): + # import pdb; pdb.set_trace() + shutil.copy(original_video_list[i], os.path.join( + origin_video_folder, original_video_list[i].split('/')[-1])) + + ori_videoclip = VideoFileClip(original_video_list[i]) + + frames = get_video_frames(original_video_list[i]) + inputs = { + 'frames': frames.unsqueeze(0).to(device) + } + pred = net(inputs).squeeze() + pred = torch.sigmoid(pred).data.cpu().numpy() + video_onsets = postprocess_video_onsets(pred, thres=0.5, nearest=4) + video_onsets = (video_onsets / 15 * 22050).astype(int) + + for ind, cond_video in enumerate([cond_video_list_0[i], cond_video_list_1[i], cond_video_list_2[i]]): + cond_video_folder = os.path.join(save_folder, f'{ind * 2 + 1}_conditional_{ind}') + os.makedirs(cond_video_folder, exist_ok=True) + shutil.copy(cond_video, os.path.join( + cond_video_folder, cond_video.split('/')[-1])) + con_videoclip = VideoFileClip(cond_video) + gen_audio = generate_audio(con_videoclip, video_onsets) + save_audio_path = 'results/gen_audio.wav' + sf.write(save_audio_path, gen_audio, 22050) + gen_audioclip = AudioFileClip(save_audio_path) + gen_videoclip = ori_videoclip.set_audio(gen_audioclip) + save_gen_folder = os.path.join(save_folder, f'{ind * 2 + 2}_generate_{ind}') + os.makedirs(save_gen_folder, exist_ok=True) + gen_videoclip.write_videofile(os.path.join(save_gen_folder, original_video_list[i].split('/')[-1])) + + + +if __name__ == '__main__': + net = models.VideoOnsetNet(pretrained=False).to(device) + resume = 'checkpoints/EXP1/checkpoint_ep100.pth.tar' + net, _ = torch_utils.load_model(resume, net, device=device, strict=True) + read_folder = '' # name to a directory that generated with `audio_generation.py` + original_video_list = glob.glob(f'{read_folder}/2sec_full_orig_video/*.mp4') + original_video_list.sort() + + cond_video_list_0 = glob.glob(f'{read_folder}/2sec_full_cond_video_0/*.mp4') + cond_video_list_0.sort() + + cond_video_list_1 = glob.glob(f'{read_folder}/2sec_full_cond_video_1/*.mp4') + cond_video_list_1.sort() + + cond_video_list_2 = glob.glob(f'{read_folder}/2sec_full_cond_video_2/*.mp4') + cond_video_list_2.sort() + + generate_video(net, original_video_list, cond_video_list_0, cond_video_list_1, cond_video_list_2) \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/onset_gen_cxav.py b/foleycrafter/models/specvqgan/onset_baseline/onset_gen_cxav.py new file mode 100644 index 0000000000000000000000000000000000000000..e82e1393d3c2ac4f6633f88f79f7ae2c59dccfd6 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/onset_gen_cxav.py @@ -0,0 +1,184 @@ +import glob +import os +import numpy as np +from moviepy.editor import * +import librosa +import soundfile as sf + +import argparse +import numpy as np +import os +import sys +import time +from tqdm import tqdm +from collections import OrderedDict + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.utils.data import DataLoader +from torch.utils.tensorboard import SummaryWriter +import torchvision.transforms as transforms +from PIL import Image +import shutil + +from config import init_args +import data +import models +from models import * +from utils import utils, torch_utils + + +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + +vision_transform_list = [ + transforms.Resize((128, 128)), + transforms.CenterCrop((112, 112)), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) +] +video_transform = transforms.Compose(vision_transform_list) + +def read_image(frame_list): + imgs = [] + convert_tensor = transforms.ToTensor() + for img_path in frame_list: + image = Image.open(img_path).convert('RGB') + image = convert_tensor(image) + imgs.append(image.unsqueeze(0)) + # (T, C, H ,W) + imgs = torch.cat(imgs, dim=0).squeeze() + imgs = video_transform(imgs) + imgs = imgs.permute(1, 0, 2, 3) + # (C, T, H ,W) + return imgs + + +def get_video_frames(origin_video_path): + save_path = 'results/temp' + if os.path.exists(save_path): + os.system(f'rm -rf {save_path}') + os.makedirs(save_path) + command = f'ffmpeg -v quiet -y -i \"{origin_video_path}\" -f image2 -vf \"scale=-1:360,fps=15\" -qscale:v 3 \"{save_path}\"/frame%06d.jpg' + os.system(command) + frame_list = glob.glob(f'{save_path}/*.jpg') + frame_list.sort() + frame_list = frame_list[:2 * 15] + frames = read_image(frame_list) + return frames + + +def postprocess_video_onsets(probs, thres=0.5, nearest=5): + # import pdb; pdb.set_trace() + video_onsets = [] + pred = np.array(probs, copy=True) + while True: + max_ind = np.argmax(pred) + video_onsets.append(max_ind) + low = max(max_ind - nearest, 0) + high = min(max_ind + nearest, pred.shape[0]) + pred[low: high] = 0 + if (pred > thres).sum() == 0: + break + video_onsets.sort() + video_onsets = np.array(video_onsets) + return video_onsets + + +def detect_onset_of_audio(audio, sample_rate): + onsets = librosa.onset.onset_detect( + y=audio, sr=sample_rate, units='samples', delta=0.3) + return onsets + + +def get_onset_audio_range(audio_len, onsets, i): + if i == 0: + prev_offset = int(onsets[i] // 3) + else: + prev_offset = int((onsets[i] - onsets[i - 1]) // 3) + + if i == onsets.shape[0] - 1: + post_offset = int((audio_len - onsets[i]) // 4 * 2) + else: + post_offset = int((onsets[i + 1] - onsets[i]) // 4 * 2) + return prev_offset, post_offset + + +def generate_audio(con_videoclip, video_onsets): + np.random.seed(2022) + con_audioclip = con_videoclip.audio + con_audio, con_sr = con_audioclip.to_soundarray(), con_audioclip.fps + con_audio = con_audio.mean(-1) + target_sr = 22050 + if target_sr != con_sr: + con_audio = librosa.resample(con_audio, orig_sr=con_sr, target_sr=target_sr) + con_sr = target_sr + + con_onsets = detect_onset_of_audio(con_audio, con_sr) + gen_audio = np.zeros(int(2 * con_sr)) + + for i in range(video_onsets.shape[0]): + prev_offset, post_offset = get_onset_audio_range( + int(con_sr * 2), video_onsets, i) + j = np.random.choice(con_onsets.shape[0]) + prev_offset_con, post_offset_con = get_onset_audio_range( + con_audio.shape[0], con_onsets, j) + prev_offset = min(prev_offset, prev_offset_con) + post_offset = min(post_offset, post_offset_con) + gen_audio[video_onsets[i] - prev_offset: video_onsets[i] + + post_offset] = con_audio[con_onsets[j] - prev_offset: con_onsets[j] + post_offset] + return gen_audio + + +def generate_video(net, original_video_list, cond_video_lists): + save_folder = 'results/onset_baseline_cxav/vis4' + os.makedirs(save_folder, exist_ok=True) + origin_video_folder = os.path.join(save_folder, '0_original') + os.makedirs(origin_video_folder, exist_ok=True) + + for i in range(len(original_video_list)): + # import pdb; pdb.set_trace() + shutil.copy(original_video_list[i], os.path.join( + origin_video_folder, cond_video_lists[0][i].split('/')[-1])) + + ori_videoclip = VideoFileClip(original_video_list[i]) + + frames = get_video_frames(original_video_list[i]) + inputs = { + 'frames': frames.unsqueeze(0).to(device) + } + pred = net(inputs).squeeze() + pred = torch.sigmoid(pred).data.cpu().numpy() + video_onsets = postprocess_video_onsets(pred, thres=0.5, nearest=4) + video_onsets = (video_onsets / 15 * 22050).astype(int) + + for ind, cond_idx in enumerate(range(len(cond_video_lists))): + cond_video = cond_video_lists[cond_idx][i] + cond_video_folder = os.path.join(save_folder, f'{ind * 2 + 1}_conditional_{ind}') + os.makedirs(cond_video_folder, exist_ok=True) + shutil.copy(cond_video, os.path.join( + cond_video_folder, cond_video.split('/')[-1])) + con_videoclip = VideoFileClip(cond_video) + gen_audio = generate_audio(con_videoclip, video_onsets) + save_audio_path = 'results/gen_audio.wav' + sf.write(save_audio_path, gen_audio, 22050) + gen_audioclip = AudioFileClip(save_audio_path) + gen_videoclip = ori_videoclip.set_audio(gen_audioclip) + save_gen_folder = os.path.join(save_folder, f'{ind * 2 + 2}_generate_{ind}') + os.makedirs(save_gen_folder, exist_ok=True) + gen_videoclip.write_videofile(os.path.join(save_gen_folder, cond_video.split('/')[-1])) + + + +if __name__ == '__main__': + net = models.VideoOnsetNet(pretrained=False).to(device) + resume = 'checkpoints/cxav_train/checkpoint_ep100.pth.tar' + net, _ = torch_utils.load_model(resume, net, device=device, strict=True) + read_folder = '' # name to a directory that generated with `audio_generation.py` + + cond_video_list_0 = glob.glob(f'{read_folder}/2sec_full_cond_video_0/*.mp4') + cond_video_list_0.sort() + original_video_list = ['_to_'.join(v.replace('2sec_full_cond_video_0', '2sec_full_orig_video').split('_to_')[:2])+'.mp4' for v in cond_video_list_0] + assert len(original_video_list) == len(cond_video_list_0) + + generate_video(net, original_video_list, [cond_video_list_0,]) \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/utils/__init__.py b/foleycrafter/models/specvqgan/onset_baseline/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..097a8993463a066fdbf215c91e723c7ee44727d8 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/utils/__init__.py @@ -0,0 +1,6 @@ +from . import sourcesep +from . import utils +from . import sound +from . import vis_utils +from . import torch_utils +from .data_sampler import ASMRSampler \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/utils/data_sampler.py b/foleycrafter/models/specvqgan/onset_baseline/utils/data_sampler.py new file mode 100644 index 0000000000000000000000000000000000000000..3c425a9c4570b93fafda1c6179554db26068be44 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/utils/data_sampler.py @@ -0,0 +1,85 @@ +import copy +import csv +import json +import numpy as np +import os +import pickle +import random + +import torch +from torch.utils.data.sampler import Sampler + +import pdb + +class ASMRSampler(Sampler): + """ + Total videos: 2794. The sampler ends when last $BATCH_SIZE videos are left. + """ + def __init__(self, list_sample, batch_size, rand_per_epoch=True): + self.list_sample = list_sample + self.batch_size = batch_size + if not rand_per_epoch: + random.seed(1234) + + self.N = len(self.list_sample) + self.sample_class_dict = self.generate_vid_dict() + # self.indexes = self.gen_index_batchwise() + # pdb.set_trace() + + def generate_vid_dict(self): + _ = [self.list_sample[i].append(i) for i in range(len(self.list_sample))] + sample_class_dict = {} + for i in range(len(self.list_sample)): + video_name = self.list_sample[i][0] + if video_name not in sample_class_dict: + sample_class_dict[video_name] = [] + sample_class_dict[video_name].append(self.list_sample[i]) + + return sample_class_dict + + def gen_index_batchwise(self): + indexes = [] + scd_copy = copy.deepcopy(self.sample_class_dict) + for i in range(self.N // self.batch_size): + if len(list(scd_copy.keys())) <= self.batch_size: + break + batch_vid = random.sample(scd_copy.keys(), self.batch_size) + for vid in batch_vid: + rand_clip = random.choice(scd_copy[vid]) + indexes.append(rand_clip[-1]) + scd_copy[vid].remove(rand_clip) # removed added element + # remove dict if empty + if len(scd_copy[vid]) == 0: + del scd_copy[vid] + + # add remain items to indexes + # for k, v in scd_copy.items(): + # for item in v: + # indexes.append(item[-1]) + return indexes + + def __iter__(self): + return iter(self.gen_index_batchwise()) + + def __len__(self): + return self.N + + +class VoxcelebSampler(Sampler): + def __init__(self, list_sample, batch_size, rand_per_epoch=True): + self.list_sample = list_sample + self.batch_size = batch_size + if not rand_per_epoch: + random.seed(1234) + + self.N = len(self.list_sample) + self.sample_class_dict = self.generate_vid_dict() + + def generate_vid_dict(self): + _ = [self.sample[i].append(i) for i in range(len(self.list_sample))] + sample_class_dict = {} + pdb.set_trace() + for i in range(len(self.list_sample)): + video_name = self.list_sample[i][0] + if video_name in batch_vid: + pdb.set_trace() \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/utils/sound.py b/foleycrafter/models/specvqgan/onset_baseline/utils/sound.py new file mode 100644 index 0000000000000000000000000000000000000000..a389c09aa21a8185ba0b4d1a63e327a8e40e4906 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/utils/sound.py @@ -0,0 +1,151 @@ +import copy +import numpy as np +import scipy.io.wavfile +import scipy.signal + +from . import utils as ut + +import pdb + +def load_sound(wav_fname): + rate, samples = scipy.io.wavfile.read(wav_fname) + times = (1./rate) * np.arange(len(samples)) + return Sound(times, rate, samples) + + +class Sound: + def __init__(self, times, rate, samples=None): + # Allow Sound(samples, sr) + if samples is None: + samples = times + times = None + if samples.dtype == np.float32: + samples = samples.astype('float64') + + self.rate = rate + # self.samples = ut.atleast_2d_col(samples) + self.samples = samples + + self.length = samples.shape[0] + if times is None: + self.times = np.arange(len(self.samples)) / float(self.rate) + else: + self.times = times + + def copy(self): + return copy.deepcopy(self) + + def parts(self): + return (self.times, self.rate, self.samples) + + def __getslice__(self, *args): + return Sound(self.times.__getslice__(*args), self.rate, + self.samples.__getslice__(*args)) + + def duration(self): + return self.samples.shape[0] / float(self.rate) + + def normalized(self, check=True): + if self.samples.dtype == np.double: + assert (not check) or np.max(np.abs(self.samples)) <= 4. + x = copy.deepcopy(self) + x.samples = np.clip(x.samples, -1., 1.) + return x + else: + s = copy.deepcopy(self) + s.samples = np.array(s.samples, 'double') / np.iinfo(s.samples.dtype).max + s.samples[s.samples < -1] = -1 + s.samples[s.samples > 1] = 1 + return s + + def unnormalized(self, dtype_name='int32'): + s = self.normalized() + inf = np.iinfo(np.dtype(dtype_name)) + samples = np.clip(s.samples, -1., 1.) + samples = inf.max * samples + samples = np.array(np.clip(samples, inf.min, inf.max), dtype_name) + s.samples = samples + return s + + def sample_from_time(self, t, bound=False): + if bound: + return min(max(0, int(np.round(t * self.rate))), self.samples.shape[0]-1) + else: + return int(np.round(t * self.rate)) + + # st = sample_from_time + + def shift_zero(self): + s = copy.deepcopy(self) + s.times -= s.times[0] + return s + + def select_channel(self, c): + s = copy.deepcopy(self) + s.samples = s.samples[:, c] + return s + + def left_pad_silence(self, n): + if n == 0: + return self.shift_zero() + else: + if np.ndim(self.samples) == 1: + samples = np.concatenate([[0] * n, self.samples]) + else: + samples = np.vstack( + [np.zeros((n, self.samples.shape[1]), self.samples.dtype), self.samples]) + return Sound(None, self.rate, samples) + + def right_pad_silence(self, n): + if n == 0: + return self.shift_zero() + else: + if np.ndim(self.samples) == 1: + samples = np.concatenate([self.samples, [0] * n]) + else: + samples = np.vstack([self.samples, np.zeros( + (n, self.samples.shape[1]), self.samples.dtype)]) + return Sound(None, self.rate, samples) + + def pad_slice(self, s1, s2): + assert s1 < self.samples.shape[0] and s2 >= 0 + s = self[max(0, s1): min(s2, self.samples.shape[0])] + s = s.left_pad_silence(max(0, -s1)) + s = s.right_pad_silence(max(0, s2 - self.samples.shape[0])) + return s + + def to_mono(self, force_copy= True): + s = copy.deepcopy(self) + s.samples = make_mono(s.samples) + return s + + def slice_time(self, t1, t2): + return self[self.st(t1): self.st(t2)] + + @property + def nchannels(self): + return 1 if np.ndim(self.samples) == 1 else self.samples.shape[1] + + def save(self, fname): + s = self.unnormalized('int16') + scipy.io.wavfile.write(fname, s.rate, s.samples.transpose()) + + def resampled(self, new_rate, clip= True): + if new_rate == self.rate: + return copy.deepcopy(self) + else: + #assert self.samples.shape[1] == 1 + return Sound(None, new_rate, self.resample(self.samples, float(new_rate)/self.rate, clip= clip)) + + def trim_to_size(self, n): + return Sound(None, self.rate, self.samples[:n]) + + def resample(self, signal, sc, clip = True, num_samples = None): + n = int(round(signal.shape[0] * sc)) if num_samples is None else num_samples + r = scipy.signal.resample(signal, n) + + if clip: + r = np.clip(r, -1, 1) + else: + r = r.astype(np.int16) + return r diff --git a/foleycrafter/models/specvqgan/onset_baseline/utils/sourcesep.py b/foleycrafter/models/specvqgan/onset_baseline/utils/sourcesep.py new file mode 100644 index 0000000000000000000000000000000000000000..d7498738c83db288ec64edbb432f763f172067bd --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/utils/sourcesep.py @@ -0,0 +1,266 @@ +import numpy as np + +import torch +import torchaudio.functional +import torchaudio +from . import utils + +import pdb + + +def stft_frame_length(pr): return int(pr.frame_length_ms * pr.samp_sr * 0.001) + +def stft_frame_step(pr): return int(pr.frame_step_ms * pr.samp_sr * 0.001) + + +def stft_num_fft(pr): return int(2**np.ceil(np.log2(stft_frame_length(pr)))) + +def log10(x): return torch.log(x)/torch.log(torch.tensor(10.)) + + +def db_from_amp(x, cuda=False): + if cuda: + return 20. * log10(torch.max(torch.tensor(1e-5).to('cuda'), x.float())) + else: + return 20. * log10(torch.max(torch.tensor(1e-5), x.float())) + + +def amp_from_db(x): + return torch.pow(10., x / 20.) + + +def norm_range(x, min_val, max_val): + return 2.*(x - min_val)/float(max_val - min_val) - 1. + +def unnorm_range(y, min_val, max_val): + return 0.5*float(max_val - min_val) * (y + 1) + min_val + +def normalize_spec(spec, pr): + return norm_range(spec, pr.spec_min, pr.spec_max) + + +def unnormalize_spec(spec, pr): + return unnorm_range(spec, pr.spec_min, pr.spec_max) + + +def normalize_phase(phase, pr): + return norm_range(phase, -np.pi, np.pi) + + +def unnormalize_phase(phase, pr): + return unnorm_range(phase, -np.pi, np.pi) + + +def normalize_ims(im): + if type(im) == type(np.array([])): + im = im.astype('float32') + else: + im = im.float() + return -1. + 2. * im + + +def stft(samples, pr, cuda=False): + spec_complex = torch.stft( + samples, + stft_num_fft(pr), + hop_length=stft_frame_step(pr), + win_length=stft_frame_length(pr)).transpose(1,2) + + real = spec_complex[..., 0] + imag = spec_complex[..., 1] + mag = torch.sqrt((real**2) + (imag**2)) + phase = utils.angle(real, imag) + if pr.log_spec: + mag = db_from_amp(mag, cuda=cuda) + return mag, phase + + +def make_complex(mag, phase): + return torch.cat(((mag * torch.cos(phase)).unsqueeze(-1), (mag * torch.sin(phase)).unsqueeze(-1)), -1) + + +def istft(mag, phase, pr): + if pr.log_spec: + mag = amp_from_db(mag) + # print(make_complex(mag, phase).shape) + samples = torchaudio.functional.istft( + make_complex(mag, phase).transpose(1,2), + stft_num_fft(pr), + hop_length=stft_frame_step(pr), + win_length=stft_frame_length(pr)) + return samples + + + +def aud2spec(sample, pr, stereo=False, norm=False, cuda=True): + sample = sample[:, :pr.sample_len] + spec, phase = stft(sample.transpose(1,2).reshape((sample.shape[0]*2, -1)), pr, cuda=cuda) + spec = spec.reshape(sample.shape[0], 2, pr.spec_len, -1) + phase = phase.reshape(sample.shape[0], 2, pr.spec_len, -1) + return spec, phase + + +def mix_sounds(samples0, pr, samples1=None, cuda=False, dominant=False, noise_ratio=0): + # pdb.set_trace() + samples0 = utils.normalize_rms(samples0, pr.input_rms) + if samples1 is not None: + samples1 = utils.normalize_rms(samples1, pr.input_rms) + + if dominant: + samples0 = samples0[:, :pr.sample_len] + samples1 = samples1[:, :pr.sample_len] * noise_ratio + else: + samples0 = samples0[:, :pr.sample_len] + samples1 = samples1[:, :pr.sample_len] + + samples_mix = (samples0 + samples1) + if cuda: + samples0 = samples0.to('cuda') + samples1 = samples1.to('cuda') + samples_mix = samples_mix.to('cuda') + + spec_mix, phase_mix = stft(samples_mix, pr, cuda=cuda) + + spec0, phase0 = stft(samples0, pr, cuda=cuda) + spec1, phase1 = stft(samples1, pr, cuda=cuda) + + spec_mix = spec_mix[:, :pr.spec_len] + phase_mix = phase_mix[:, :pr.spec_len] + spec0 = spec0[:, :pr.spec_len] + spec1 = spec1[:, :pr.spec_len] + phase0 = phase0[:, :pr.spec_len] + phase1 = phase1[:, :pr.spec_len] + + return utils.Struct( + samples=samples_mix.float(), + phase=phase_mix.float(), + spec=spec_mix.float(), + sample_parts=[samples0, samples1], + spec_parts=[spec0.float(), spec1.float()], + phase_parts=[phase0.float(), phase1.float()]) + + +def pit_loss(pred_spec_fg, pred_spec_bg, snd, pr, cuda=True, vis=False): + # if pr.norm_spec: + def ns(x): return normalize_spec(x, pr) + # else: + # def ns(x): return x + if pr.norm: + gts_ = [[ns(snd.spec_parts[0]), None], + [ns(snd.spec_parts[1]), None]] + preds = [[ns(pred_spec_fg), None], + [ns(pred_spec_bg), None]] + else: + gts_ = [[snd.spec_parts[0], None], + [snd.spec_parts[1], None]] + preds = [[pred_spec_fg, None], + [pred_spec_bg, None]] + + def l1(x, y): return torch.mean(torch.abs(x - y), (1, 2)) + losses = [] + for i in range(2): + gt = [gts_[i % 2], gts_[(i+1) % 2]] + fg_spec = pr.l1_weight * l1(preds[0][0], gt[0][0]) + bg_spec = pr.l1_weight * l1(preds[1][0], gt[1][0]) + losses.append(fg_spec + bg_spec) + + losses = torch.cat([x.unsqueeze(0) for x in losses], dim=0) + if vis: + print(losses) + loss_val = torch.min(losses, dim=0) + if vis: + print(loss_val[1]) + loss = torch.mean(loss_val[0]) + + return loss + + +def diff_loss(spec_diff, phase_diff, snd, pr, device, norm=False, vis=False): + def ns(x): return normalize_spec(x, pr) + def np(x): return normalize_phase(x, pr) + criterion = torch.nn.L1Loss() + + gt_spec_diff = snd.spec_diff + gt_phase_diff = snd.phase_diff + criterion = criterion.to(device) + + if norm: + gt_spec_diff = ns(gt_spec_diff) + gt_phase_diff = np(gt_phase_diff) + pred_spec_diff = ns(spec_diff) + pred_phase_diff = np(phase_diff) + else: + pred_spec_diff = spec_diff + pred_phase_diff = phase_diff + + spec_loss = criterion(pred_spec_diff, gt_spec_diff) + phase_loss = criterion(pred_phase_diff, gt_phase_diff) + loss = pr.l1_weight * spec_loss + pr.phase_weight * phase_loss + if vis: + print(loss) + return loss + +# def pit_loss(out, snd, pr, cuda=False, vis=False): +# def ns(x): return normalize_spec(x, pr) +# def np(x): return normalize_phase(x, pr) +# if cuda: +# snd['spec_part0'] = snd['spec_part0'].to('cuda') +# snd['phase_part0'] = snd['phase_part0'].to('cuda') +# snd['spec_part1'] = snd['spec_part1'].to('cuda') +# snd['phase_part1'] = snd['phase_part1'].to('cuda') +# # gts_ = [[ns(snd['spec_part0'][:, 0, :, :]), np(snd['phase_part0'][:, 0, :, :])], +# # [ns(snd['spec_part1'][:, 0, :, :]), np(snd['phase_part1'][:, 0, :, :])]] +# gts_ = [[ns(snd.spec_parts[0]), np(snd.phase_parts[0])], +# [ns(snd.spec_parts[1]), np(snd.phase_parts[1])]] +# preds = [[ns(out.pred_spec_fg), np(out.pred_phase_fg)], +# [ns(out.pred_spec_bg), np(out.pred_phase_bg)]] + +# def l1(x, y): return torch.mean(torch.abs(x - y), (1, 2)) +# losses = [] +# for i in range(2): +# gt = [gts_[i % 2], gts_[(i+1) % 2]] +# # print 'preds[0][0] shape =', shape(preds[0][0]) +# # fg_spec = pr.l1_weight * l1(preds[0][0], gt[0][0]) +# # fg_phase = pr.phase_weight * l1(preds[0][1], gt[0][1]) + +# # bg_spec = pr.l1_weight * l1(preds[1][0], gt[1][0]) +# # bg_phase = pr.phase_weight * l1(preds[1][1], gt[1][1]) + +# # losses.append(fg_spec + fg_phase + bg_spec + bg_phase) +# fg_spec = pr.l1_weight * l1(preds[0][0], gt[0][0]) + +# bg_spec = pr.l1_weight * l1(preds[1][0], gt[1][0]) + +# losses.append(fg_spec + bg_spec) +# # pdb.set_trace() +# # pdb.set_trace() +# losses = torch.cat([x.unsqueeze(0) for x in losses], dim=0) +# if vis: +# print(losses) +# loss_val = torch.min(losses, dim=0) +# if vis: +# print(loss_val[1]) +# loss = torch.mean(loss_val[0]) + +# return loss + +# def stereo_mel() + + +def audio_stft(stft, audio, pr): + N, C, A = audio.size() + audio = audio.view(N * C, A) + spec = stft(audio) + spec = spec.transpose(-1, -2) + spec = db_from_amp(spec, cuda=True) + spec = normalize_spec(spec, pr) + _, T, F = spec.size() + spec = spec.view(N, C, T, F) + return spec + + +def normalize_audio(samples, desired_rms=0.1, eps=1e-4): + # print(np.mean(samples**2)) + rms = np.maximum(eps, np.sqrt(np.mean(samples**2))) + samples = samples * (desired_rms / rms) + return samples \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/utils/torch_utils.py b/foleycrafter/models/specvqgan/onset_baseline/utils/torch_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..4137e54d86b0ef520868c79f264c04852c590723 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/utils/torch_utils.py @@ -0,0 +1,113 @@ +from collections import OrderedDict +import os +import numpy as np +import random +import sys + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.utils.data import DataLoader + +sys.path.append('..') +import data + + +# ---------------------------------------------------- # +def load_model(cp_path, net, device=None, strict=True): + if not device: + device = torch.device('cpu') + if os.path.isfile(cp_path): + print("=> loading checkpoint '{}'".format(cp_path)) + checkpoint = torch.load(cp_path, map_location=device) + + # check if there is module + if list(checkpoint['state_dict'].keys())[0][:7] == 'module.': + state_dict = OrderedDict() + for k, v in checkpoint['state_dict'].items(): + name = k[7:] + state_dict[name] = v + else: + state_dict = checkpoint['state_dict'] + net.load_state_dict(state_dict, strict=strict) + + print("=> loaded checkpoint '{}' (epoch {})" + .format(cp_path, checkpoint['epoch'])) + start_epoch = checkpoint['epoch'] + else: + print("=> no checkpoint found at '{}'".format(cp_path)) + start_epoch = 0 + sys.exit() + + return net, start_epoch + + +# ---------------------------------------------------- # +def binary_acc(pred, target, thred): + pred = pred > thred + acc = np.sum(pred == target) / target.shape[0] + return acc + +def calc_acc(prob, labels, k): + pred = torch.argsort(prob, dim=-1, descending=True)[..., :k] + top_k_acc = torch.sum(pred == labels.view(-1, 1)).float() / labels.size(0) + return top_k_acc + +# ---------------------------------------------------- # + +def get_dataloader(args, pr, split='train', shuffle=False, drop_last=False, batch_size=None): + data_loader = getattr(data, pr.dataloader) + if split == 'train': + read_list = pr.list_train + elif split == 'val': + read_list = pr.list_val + elif split == 'test': + read_list = pr.list_test + dataset = data_loader(args, pr, read_list, split=split) + batch_size = batch_size if batch_size else args.batch_size + dataset.getitem_test(1) + loader = DataLoader( + dataset, + batch_size=batch_size, + shuffle=shuffle, + num_workers=args.num_workers, + pin_memory=True, + drop_last=drop_last) + + return dataset, loader + + +# ---------------------------------------------------- # +def make_optimizer(model, args): + ''' + Args: + model: NN to train + Returns: + optimizer: pytorch optmizer for updating the given model parameters. + ''' + if args.optim == 'SGD': + optimizer = torch.optim.SGD( + filter(lambda p: p.requires_grad, model.parameters()), + lr=args.lr, + momentum=args.momentum, + weight_decay=args.weight_decay, + nesterov=False + ) + elif args.optim == 'Adam': + optimizer = torch.optim.Adam( + filter(lambda p: p.requires_grad, model.parameters()), + lr=args.lr, + weight_decay=args.weight_decay, + ) + return optimizer + + +def adjust_learning_rate(optimizer, epoch, args): + """Decay the learning rate based on schedule""" + lr = args.lr + if args.schedule == 'cos': # cosine lr schedule + lr *= 0.5 * (1. + np.cos(np.pi * epoch / args.epochs)) + elif args.schedule == 'none': # no lr schedule + lr = args.lr + for param_group in optimizer.param_groups: + param_group['lr'] = lr \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/utils/utils.py b/foleycrafter/models/specvqgan/onset_baseline/utils/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..a9f7e72a27f3ff0954606d473a2a953fa4127590 --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/utils/utils.py @@ -0,0 +1,158 @@ +import copy +import errno +import inspect +import numpy as np +import os +import sys + +import torch + +import pdb + + +class LoggerOutput(object): + def __init__(self, fpath=None): + self.console = sys.stdout + self.file = None + if fpath is not None: + self.mkdir_if_missing(os.path.dirname(fpath)) + self.file = open(fpath, 'w') + + def __del__(self): + self.close() + + def __enter__(self): + pass + + def __exit__(self, *args): + self.close() + + def write(self, msg): + self.console.write(msg) + if self.file is not None: + self.file.write(msg) + + def flush(self): + self.console.flush() + if self.file is not None: + self.file.flush() + os.fsync(self.file.fileno()) + + def close(self): + self.console.close() + if self.file is not None: + self.file.close() + + def mkdir_if_missing(self, dir_path): + try: + os.makedirs(dir_path) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + +class AverageMeter(object): + """Computes and stores the average and current value""" + + def __init__(self): + self.initialized = False + self.val = None + self.avg = None + self.sum = None + self.count = None + + def initialize(self, val, weight): + self.val = val + self.avg = val + self.sum = val*weight + self.count = weight + self.initialized = True + + def update(self, val, weight=1): + val = np.asarray(val) + if not self.initialized: + self.initialize(val, weight) + else: + self.add(val, weight) + + def add(self, val, weight): + self.val = val + self.sum += val * weight + self.count += weight + self.avg = self.sum / self.count + + def value(self): + if self.val is None: + return 0. + else: + return self.val.tolist() + + def average(self): + if self.avg is None: + return 0. + else: + return self.avg.tolist() + + +class Struct: + def __init__(self, *dicts, **fields): + for d in dicts: + for k, v in d.iteritems(): + setattr(self, k, v) + self.__dict__.update(fields) + + def to_dict(self): + return {a: getattr(self, a) for a in self.attrs()} + + def attrs(self): + #return sorted(set(dir(self)) - set(dir(Struct))) + xs = set(dir(self)) - set(dir(Struct)) + xs = [x for x in xs if ((not (hasattr(self.__class__, x) and isinstance(getattr(self.__class__, x), property))) \ + and (not inspect.ismethod(getattr(self, x))))] + return sorted(xs) + + def updated(self, other_struct_=None, **kwargs): + s = copy.deepcopy(self) + if other_struct_ is not None: + s.__dict__.update(other_struct_.to_dict()) + s.__dict__.update(kwargs) + return s + + def copy(self): + return copy.deepcopy(self) + + def __str__(self): + attrs = ', '.join('%s=%s' % (a, getattr(self, a)) for a in self.attrs()) + return 'Struct(%s)' % attrs + + +class Params(Struct): + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + +def normalize_rms(samples, desired_rms=0.1, eps=1e-4): + rms = torch.max(torch.tensor(eps), torch.sqrt( + torch.mean(samples**2, dim=1)).float()) + samples = samples * desired_rms / rms.unsqueeze(1) + return samples + + +def normalize_rms_np(samples, desired_rms=0.1, eps=1e-4): + rms = np.maximum(eps, np.sqrt(np.mean(samples**2, 1))) + samples = samples * (desired_rms / rms) + return samples + + +def angle(real, imag): + return torch.atan2(imag, real) + + +def atleast_2d_col(x): + x = np.asarray(x) + if np.ndim(x) == 0: + return x[np.newaxis, np.newaxis] + if np.ndim(x) == 1: + return x[:, np.newaxis] + else: + return x diff --git a/foleycrafter/models/specvqgan/onset_baseline/utils/vis_utils.py b/foleycrafter/models/specvqgan/onset_baseline/utils/vis_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..67b95108fa71cb842eb405545bfca799b922fd4e --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/utils/vis_utils.py @@ -0,0 +1,706 @@ +import copy +import cv2 +import itertools as itl +import json +import kornia as K +from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas +import matplotlib.pyplot as plt +import numpy as np +import os +from pathlib import Path +import PIL +from PIL import Image, ImageDraw, ImageFont +import pylab +import random + +import torch + +import pdb + +def clip_rescale(x, lo = None, hi = None): + if lo is None: + lo = np.min(x) + if hi is None: + hi = np.max(x) + return np.clip((x - lo)/(hi - lo), 0., 1.) + +def apply_cmap(im, cmap = pylab.cm.jet, lo = None, hi = None): + return cmap(clip_rescale(im, lo, hi).flatten()).reshape(im.shape[:2] + (-1,))[:, :, :3] + +def cmap_im(cmap, im, lo = None, hi = None): + return np.uint8(255*apply_cmap(im, cmap, lo, hi)) + +def calc_acc(prob, labels, k=1): + thred = 0.5 + pred = torch.argsort(prob, dim=-1, descending=True)[..., :k] + corr = (pred.view(-1) == labels).cpu().numpy() + corr = corr.reshape((-1, resol*resol)) + acc = corr.sum(1) / (resol*resol) # compute rate of successful patch for each image + corr_index = np.where((acc > thred) == True)[0] + return corr_index + +# def compute_acc_list(A_IS, k=0): +# criterion = nn.NLLLoss() +# M, N = A_IS.size() +# target = torch.from_numpy(np.repeat(np.eye(N), M // N, axis=0)).to(DEVICE) +# _, labels = target.max(dim=1) +# loss = criterion(torch.log(A_IS), labels.long()) +# acc = None +# if k > 0: +# corr_index = calc_acc(A_IS, labels, k) +# return corr_index + +def get_fcn_sim(full_img, feat_audio, net, B, resol, norm=True): + feat_img = net.forward_fcn(full_img) + feat_img = feat_img.permute(0, 2,3,1).reshape(-1, 128) + A_II, A_IS, A_SI = net.GetAMatrix(feat_img, feat_audio, norm=norm) + A_IS_ = A_IS.reshape((B, resol*resol, B)) + A_IIS_ = (A_II @ A_IS).reshape((B, resol*resol, B)) + A_II_ = A_II.reshape((B, resol*resol, B*resol*resol)) + + return A_IS_, A_IIS_, A_II_ + +def upsample_lowest(sim, im_h, im_w, pr): + sim_h, sim_w = sim.shape + prob_map_per_patch = np.zeros((im_h, im_w, pr.resol*pr.resol)) + # pdb.set_trace() + for i in range(pr.resol): + for j in range(pr.resol): + y1 = pr.patch_stride * i + y2 = pr.patch_stride * i + pr.psize + x1 = pr.patch_stride * j + x2 = pr.patch_stride * j + pr.psize + prob_map_per_patch[y1:y2, x1:x2, i * pr.resol + j] = sim[i, j] + # pdb.set_trace() + upsampled = np.sum(prob_map_per_patch, axis=-1) / np.sum(prob_map_per_patch > 0, axis=-1) + + return upsampled + + +def grid_interp(pr, input, output_size, mode='bilinear'): + # import pdb; pdb.set_trace() + n = 1 + c = 1 + ih, iw = input.shape + input = input.view(n, c, ih, iw) + oh, ow = output_size + + pad = (pr.psize - pr.patch_stride) // 2 + ch = oh - pad * 2 + cw = ow - pad * 2 + # normalize to [-1, 1] + h = (torch.arange(0, oh) - pad) / (ch-1) * 2 - 1 + w = (torch.arange(0, ow) - pad) / (cw-1) * 2 - 1 + + grid = torch.zeros(oh, ow, 2) + grid[:, :, 0] = w.unsqueeze(0).repeat(oh, 1) + grid[:, :, 1] = h.unsqueeze(0).repeat(ow, 1).transpose(0, 1) + grid = grid.unsqueeze(0).repeat(n, 1, 1, 1) # grid.shape: [n, oh, ow, 2] + grid = grid.to(input.device) + res = torch.nn.functional.grid_sample(input, grid, mode=mode, padding_mode="border", align_corners=False).squeeze() + return res + + +def upsample_lowest_torch(sim, im_h, im_w, pr): + sim = sim.reshape(pr.resol*pr.resol) + # precompute the temeplate + prob_map_per_patch = torch.from_numpy(pr.template).to('cuda') + prob_map_per_patch = prob_map_per_patch * sim.reshape(1,1,-1) + upsampled = torch.sum(prob_map_per_patch, dim=-1) / torch.sum(prob_map_per_patch > 0, dim=-1) + + return upsampled + + +def gen_vis_map(prob, im_h, im_w, pr, bound=False, lo=0, hi=0.3, mode='nearest'): + """ + prob: probability map for patches + im_h, im_w: original image size + resol: resolution of patches + bound: whether to give low and high bound for probability + lo: + hi: + mode: upsample method for probability + """ + resol = pr.resol + if mode == 'nearest': + resample = PIL.Image.NEAREST + elif mode == 'bilinear': + resample = PIL.Image.BILINEAR + sim = prob.reshape((resol, resol)) + # pdb.set_trace() + # updample similarity + if mode in ['nearest', 'bilinear']: + if torch.is_tensor(sim): + sim = sim.cpu().numpy() + sim_up = np.array(Image.fromarray(sim).resize((im_w, im_h), resample=resample)) + elif mode == 'lowest': + sim_up = upsample_lowest_torch(sim, im_w, im_h, pr) + sim_up = sim_up.detach().cpu().numpy() + elif mode == 'grid': + sim_up = grid_interp(pr, sim, (im_h, im_w), 'bilinear') + sim_up = sim_up.detach().cpu().numpy() + + if not bound: + lo = None + hi = None + # generate heat map + # pdb.set_trace() + vis = cmap_im(pylab.cm.jet, sim_up, lo=lo, hi=hi) + + # p weights the cmap on original image + p = sim_up / sim_up.max() * 0.3 + 0.3 + p = p[..., None] + + return p, vis + + +def gen_upsampled_prob(prob, im_h, im_w, pr, bound=False, lo=0, hi=0.3, mode='nearest'): + """ + prob: probability map for patches + im_h, im_w: original image size + resol: resolution of patches + bound: whether to give low and high bound for probability + lo: + hi: + mode: upsample method for probability + """ + resol = pr.resol + if mode == 'nearest': + resample = PIL.Image.NEAREST + elif mode == 'bilinear': + resample = PIL.Image.BILINEAR + sim = prob.reshape((resol, resol)) + # pdb.set_trace() + # updample similarity + if mode in ['nearest', 'bilinear']: + if torch.is_tensor(sim): + sim = sim.cpu().numpy() + sim_up = np.array(Image.fromarray(sim).resize((im_w, im_h), resample=resample)) + elif mode == 'lowest': + sim_up = upsample_lowest_torch(sim, im_w, im_h, pr) + sim_up = sim_up.cpu().numpy() + sim_up = sim_up / sim_up.max() + return sim_up + + +def gen_vis_map_probmap_up(prob_up, bound=False, lo=0, hi=0.3, mode='nearest'): + if mode == 'nearest': + resample = PIL.Image.NEAREST + elif mode == 'bilinear': + resample = PIL.Image.BILINEAR + if not bound: + lo = None + hi = None + vis = cmap_im(pylab.cm.jet, prob_up, lo=None, hi=None) + if bound: + # when hi gets larger, cmap becomes less visibal + p = prob_up / prob_up.max() * (0.3+0.4*(1-hi)) + 0.3 + else: + # if not bound, cmap always weights 0.3 on original image + p = prob_up / prob_up.max() * 0.3 + 0.3 + p = p[..., None] + + return p, vis + + +def rgb2bgr(im): + return cv2.cvtColor(im, cv2.COLOR_RGB2BGR) + +def gen_bbox_patches(im, patch_ind, resol, patch_size=64, lin_w=3, lin_color=np.array([255,0,0])): + # TODO: make it work for different image size + stride = int((256-patch_size)/(resol-1)) + + im_w, im_h = im.shape[1], im.shape[0] + + r_ind = patch_ind // resol + c_ind = patch_ind % resol + y1 = r_ind * stride + y2 = r_ind * stride + patch_size + x1 = c_ind * stride + x2 = c_ind * stride + patch_size + + im_bbox = copy.deepcopy(im) + im_bbox[y1:y1+lin_w, x1:x2, :] = lin_color + im_bbox[y2-lin_w:y2, x1:x2, :] = lin_color + im_bbox[y1:y2, x1:x1+lin_w, :] = lin_color + im_bbox[y1:y2, x2-lin_w:x2, :] = lin_color + + return (x1, y1, x2-x1, y2-y1), im_bbox + +def get_fcn_sim(full_img, feat_audio, net, B, resol, norm=True): + feat_img = net.forward_fcn(full_img) + feat_img = feat_img.permute(0, 2,3,1).reshape(-1, 128) + A_II, A_IS, A_SI = net.GetAMatrix(feat_img, feat_audio, norm=norm) + A_IS_ = A_IS.reshape((B, resol*resol, B)) + A_IIS_ = (A_II @ A_IS).reshape((B, resol*resol, B)) + A_II_ = A_II.reshape((B, resol*resol, B, resol*resol)) + return A_IS_, A_IIS_, A_II_ + +def put_text(im, text, loc, font_scale=4): + fontScale = font_scale + thickness = int(fontScale / 4) + fontColor = (0,255,255) + lineType = 4 + im = cv2.putText(im, text, loc, cv2.FONT_HERSHEY_SIMPLEX, fontScale, fontColor, thickness, lineType) + return im + +def im2video(save_path, frame_list, fps=5): + height, width, _ = frame_list[0].shape + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + video = cv2.VideoWriter(save_path, fourcc, fps, (width, height)) + + for frame in frame_list: + video.write(rgb2bgr(frame)) + + cv2.destroyAllWindows() + video.release() + new_name = "{}_new{}".format(save_path[:-4], save_path[-4:]) + os.system("ffmpeg -v quiet -y -i \"{}\" -pix_fmt yuv420p -vcodec h264 -strict -2 -acodec aac \"{}\"".format(save_path, new_name)) + os.system("rm -rf \"{}\"".format(save_path)) + +def get_face_landmark(frame_path_): + video_folder = Path(frame_path_).parent.parent + frame_name = frame_path_.split('/')[-1] + face_landmark_path = os.path.join(video_folder, "face_bbox_landmark.json") + if not os.path.exists(face_landmark_path): + return None + with open(face_landmark_path, 'r') as f: + face_landmark = json.load(f) + if len(face_landmark[frame_name]) == 0: + return None + b = face_landmark[frame_name][0] + return b + +def make_color_wheel(): + # same source as color_flow + + RY = 15 + YG = 6 + GC = 4 + CB = 11 + BM = 13 + MR = 6 + + ncols = RY + YG + GC + CB + BM + MR + + #colorwheel = zeros(ncols, 3) # r g b + # matlab correction + colorwheel = np.zeros((1+ncols, 4)) # r g b + + col = 0 + #RY + colorwheel[1:1+RY, 1] = 255 + colorwheel[1:1+RY, 2] = np.floor(255*np.arange(0, 1+RY-1)/RY).T + col = col+RY + + #YG + colorwheel[col+1:col+1+YG, 1] = 255 - np.floor(255*np.arange(0,1+YG-1)/YG).T + colorwheel[col+1:col+1+YG, 2] = 255 + col = col+YG + + #GC + colorwheel[col+1:col+1+GC, 2] = 255 + colorwheel[col+1:col+1+GC, 3] = np.floor(255*np.arange(0,1+GC-1)/GC).T + col = col+GC + + #CB + colorwheel[col+1:col+1+CB, 2] = 255 - np.floor(255*np.arange(0,1+CB-1)/CB).T + colorwheel[col+1:col+1+CB, 3] = 255 + col = col+CB + + #BM + colorwheel[col+1:col+1+BM, 3] = 255 + colorwheel[col+1:col+1+BM, 1] = np.floor(255*np.arange(0,1+BM-1)/BM).T + col = col+BM + + #MR + colorwheel[col+1:col+1+MR, 3] = 255 - np.floor(255*np.arange(0,1+MR-1)/MR).T + colorwheel[col+1:col+1+MR, 1] = 255 + + # 1-based to 0-based indices + return colorwheel[1:, 1:] + +def warp(im, flow): + # im : C x H x W + # flow : 2 x H x W, such that flow[dst_y, dst_x] = (src_x, src_y), + # where (src_x, src_y) is the pixel location we want to sample from. + + # grid_sample the grid is in the range in [-1, 1] + grid = -1. + 2. * flow/(-1 + np.array([im.shape[2], im.shape[1]], np.float32))[:, None, None] + + # print('grid range =', grid.min(), grid.max()) + ft = torch.FloatTensor + warped = torch.nn.functional.grid_sample( + ft(im[None].astype(np.float32)), + ft(grid.transpose((1, 2, 0))[None]), + mode = 'bilinear', padding_mode = 'zeros', align_corners=True) + return warped.cpu().numpy()[0].astype(im.dtype) + +def compute_color(u, v): + # from same source as color_flow; please see above comment + # nan_idx = ut.lor(np.isnan(u), np.isnan(v)) + nan_idx = np.logical_or(np.isnan(u), np.isnan(v)) + u[nan_idx] = 0 + v[nan_idx] = 0 + colorwheel = make_color_wheel() + ncols = colorwheel.shape[0] + + rad = np.sqrt(u**2 + v**2) + + a = np.arctan2(-v, -u)/np.pi + + #fk = (a + 1)/2. * (ncols-1) + 1 + fk = (a + 1)/2. * (ncols-1) + + k0 = np.array(np.floor(fk), 'l') + + k1 = k0 + 1 + k1[k1 == ncols] = 1 + + f = fk - k0 + + im = np.zeros(u.shape + (3,)) + + for i in range(colorwheel.shape[1]): + tmp = colorwheel[:, i] + col0 = tmp[k0]/255. + col1 = tmp[k1]/255. + col = (1-f)*col0 + f*col1 + + idx = rad <= 1 + col[idx] = 1 - rad[idx]*(1-col[idx]) + col[np.logical_not(idx)] *= 0.75 + im[:, :, i] = np.uint8(np.floor(255*col*(1-nan_idx))) + + return im + +def color_flow(flow, max_flow = None): + flow = flow.copy() + # based on flowToColor.m by Deqing Sun, orignally based on code by Daniel Scharstein + UNKNOWN_FLOW_THRESH = 1e9 + UNKNOWN_FLOW = 1e10 + height, width, nbands = flow.shape + assert nbands == 2 + u, v = flow[:,:,0], flow[:,:,1] + maxu = -999. + maxv = -999. + minu = 999. + minv = 999. + maxrad = -1. + + idx_unknown = np.logical_or(np.abs(u) > UNKNOWN_FLOW_THRESH, np.abs(v) > UNKNOWN_FLOW_THRESH) + u[idx_unknown] = 0 + v[idx_unknown] = 0 + + maxu = max(maxu, np.max(u)) + maxv = max(maxv, np.max(v)) + + minu = min(minu, np.min(u)) + minv = min(minv, np.min(v)) + + rad = np.sqrt(u**2 + v**2) + maxrad = max(maxrad, np.max(rad)) + + if max_flow > 0: + maxrad = max_flow + + u = u/(maxrad + np.spacing(1)) + v = v/(maxrad + np.spacing(1)) + + im = compute_color(u, v) + im[idx_unknown] = 0 + return im + +def plt_fig_to_np_img(fig): + canvas = FigureCanvas(fig) # draw the canvas, cache the renderer + canvas.draw() + width, height = fig.get_size_inches() * fig.get_dpi() + image = np.fromstring(canvas.tostring_rgb(), dtype='uint8') + image = image.reshape(int(height), int(width), 3) + + return image + +def save_np_img(image, path): + cv2.imwrite(path, rgb2bgr(image)) + +def find_patch_topk_aud(mat, top_k): + top_k_ind = torch.argsort(mat, dim=-1, descending=True)[..., :top_k].squeeze() + top_k_ind = top_k_ind.reshape(-1).cpu().numpy() + return top_k_ind + +def find_patch_pred_topk(mat, top_k, target): + M, N = mat.size() + labels = torch.from_numpy(target * np.ones(M)).to('cuda') + top_k_ind = torch.sum(torch.argsort(mat, dim=-1, descending=True)[..., :top_k] == labels.view(-1, 1), dim=-1).nonzero().reshape(-1) + top_k_ind = top_k_ind.reshape(-1).cpu().numpy() + return top_k_ind + +def gen_masked_img(mask_ind, resol, img): + mask = torch.zeros(resol*resol) + mask = mask.scatter_(0, torch.from_numpy(mask_ind), 1.) + mask = mask.reshape(resol, resol).numpy() + img_h = img.shape[1] + img_w = img.shape[0] + mask_up = np.array(Image.fromarray(mask*255).resize((img_h, img_w), resample=PIL.Image.NEAREST)) + mask_up = mask_up[..., None] + image_seg = np.uint8(img * 0.7 + mask_up * 0.3) + + return image_seg + +def drop_2rand_ch(patch, remain_c=0): + B, P, C, H, W = patch.shape + patch_c = patch[:, :, remain_c, :, :].unsqueeze(2) + # patch_droped = torch.zeros_like(patch) + # patch_droped[:, :, remain_c, :, :] = patch_c + c_std = torch.std(patch_c, dim=(3,4)) + gauss_n = 0.5 + (0.01 * c_std.reshape(B, P, 1, 1, 1) * torch.randn(B, P, 2, H, W).to('cuda')) + + patch_dropped = torch.cat([gauss_n[:, :, :remain_c], patch_c, gauss_n[:, :, remain_c:]], dim=2) + + return patch_dropped + # pdb.set_trace() + +def vis_patch(patch, exp_path, resol, b_step): + B, P, C, H, W = patch.shape + for i in range(B): + patch_i = patch[i].reshape(resol, resol, C, H, W) + patch_i = patch_i.permute(2, 0, 3, 1, 4) + patch_folded_i = patch_i.reshape(C, resol*H, resol*W) + patch_folded_i = (patch_folded_i * 255).cpu().numpy().astype(np.uint8).transpose(1,2,0) + cv2.imwrite('{}/{}_{}_patch_folded.jpg'.format(exp_path, str(b_step).zfill(4), str(i).zfill(4)), rgb2bgr(patch_folded_i)) + +def blur_patch(patch, k_size=3, sigma=0.5): + B, P, C, H, W = patch.shape + gauss = K.filters.GaussianBlur2d((k_size, k_size), (sigma, sigma)) + patch = patch.reshape(B*P, C, H, W) + blur_patch = gauss(patch).reshape(B, P, C, H, W) + return blur_patch + +def gray_project_patch(patch, device): + N, P, C, H, W = patch.size() + a = torch.tensor([[-1, 2, -1]]).float() + B = (torch.eye(3) - (a.T @ a) / (a @ a.T)).to(device) + patch = patch.permute(0, 1, 3, 4, 2) + patch = (patch @ B).permute(0, 1, 4, 2, 3) + return patch + +def parse_color(c): + if type(c) == type((0,)) or type(c) == type(np.array([1])): + return c + elif type(c) == type(''): + return color_from_string(c) + +def colors_from_input(color_input, default, n): + """ Parse color given as input argument; gives user several options """ + # todo: generalize this to non-colors + expanded = None + if color_input is None: + expanded = [default] * n + elif (type(color_input) == type((1,))) and map(type, color_input) == [int, int, int]: + # expand (r, g, b) -> [(r, g, b), (r, g, b), ..] + expanded = [color_input] * n + else: + # general case: [(r1, g1, b1), (r2, g2, b2), ...] + expanded = color_input + + expanded = map(parse_color, expanded) + return expanded + +def draw_pts(im, points, colors = None, width = 1, texts = None): + # ut.check(colors is None or len(colors) == len(points)) + points = list(points) + colors = colors_from_input(colors, (255, 0, 0), len(points)) + rects = [(p[0] - width/2, p[1] - width/2, width, width) for p in points] + return draw_rects(im, rects, fills = colors, outlines = [None]*len(points), texts = texts) + +def to_pil(im): + #print im.dtype + return Image.fromarray(np.uint8(im)) + +def from_pil(pil): + #print pil + return np.array(pil) + +def draw_on(f, im): + pil = to_pil(im) + draw = ImageDraw.ImageDraw(pil) + f(draw) + return from_pil(pil) + +def fail(s = ''): raise RuntimeError(s) + +def check(cond, str = 'Check failed!'): + if not cond: + fail(str) + +def draw_rects(im, rects, outlines = None, fills = None, texts = None, text_colors = None, line_widths = None, as_oval = False): + rects = list(rects) + outlines = colors_from_input(outlines, (0, 0, 255), len(rects)) + outlines = list(outlines) + text_colors = colors_from_input(text_colors, (255, 255, 255), len(rects)) + text_colors = list(text_colors) + fills = colors_from_input(fills, None, len(rects)) + fills = list(fills) + + if texts is None: texts = [None] * len(rects) + if line_widths is None: line_widths = [None] * len(rects) + + def check_size(x, s): + check(x is None or len(list(x)) == len(rects), "%s different size from rects" % s) + check_size(outlines, 'outlines') + check_size(fills, 'fills') + check_size(texts, 'texts') + check_size(text_colors, 'texts') + + def f(draw): + for (x, y, w, h), outline, fill, text, text_color, lw in zip(rects, outlines, fills, texts, text_colors, line_widths): + if lw is None: + if as_oval: + draw.ellipse((x, y, x + w, y + h), outline = outline, fill = fill) + else: + draw.rectangle((x, y, x + w, y + h), outline = outline, fill = fill) + else: + d = int(np.ceil(lw/2)) + draw.rectangle((x-d, y-d, x+w+d, y+d), fill = outline) + draw.rectangle((x-d, y-d, x+d, y+h+d), fill = outline) + + draw.rectangle((x+w+d, y+h+d, x-d, y+h-d), fill = outline) + draw.rectangle((x+w+d, y+h+d, x+w-d, y-d), fill = outline) + + if text is not None: + # draw text inside rectangle outline + border_width = 2 + draw.text((border_width + x, y), text, fill = text_color) + return draw_on(f, im) + +def rand_color(): + return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + +def int_tuple(x): + return tuple([int(v) for v in x]) + +itup = int_tuple + +red = (255, 0, 0) +green = (0, 255, 0) +blue = (0, 0, 255) +yellow = (255, 255, 0) +purple = (255, 0, 255) +cyan = (0, 255, 255) + + +def stash_seed(new_seed = 0): + """ Sets the random seed to new_seed. Returns the old seed. """ + if type(new_seed) == type(''): + new_seed = hash(new_seed) % 2**32 + + py_state = random.getstate() + random.seed(new_seed) + + np_state = np.random.get_state() + np.random.seed(new_seed) + return (py_state, np_state) + + +def do_with_seed(f, seed = 0): + old_seed = stash_seed(seed) + res = f() + unstash_seed(old_seed[0], old_seed[1]) + return res + +def sample_at_most(xs, bound): + return random.sample(xs, min(bound, len(xs))) + +class ColorChooser: + def __init__(self, dist_thresh = 500, attempts = 500, init_colors = [], init_pts = []): + self.pts = init_pts + self.colors = init_colors + self.attempts = attempts + self.dist_thresh = dist_thresh + + def choose(self, new_pt = (0, 0)): + new_pt = np.array(new_pt) + nearby_colors = [] + for pt, c in zip(self.pts, self.colors): + if np.sum((pt - new_pt)**2) <= self.dist_thresh**2: + nearby_colors.append(c) + + if len(nearby_colors) == 0: + color_best = rand_color() + else: + nearby_colors = np.array(sample_at_most(nearby_colors, 100), 'l') + choices = np.array(np.random.rand(self.attempts, 3)*256, 'l') + dists = np.sqrt(np.sum((choices[:, np.newaxis, :] - nearby_colors[np.newaxis, :, :])**2, axis = 2)) + costs = np.min(dists, axis = 1) + assert costs.shape == (len(choices),) + color_best = itup(choices[np.argmax(costs)]) + + self.pts.append(new_pt) + self.colors.append(color_best) + return color_best + +def unstash_seed(py_state, np_state): + random.setstate(py_state) + np.random.set_state(np_state) + +def distinct_colors(n): + #cc = ColorChooser(attempts = 10, init_colors = [red, green, blue, yellow, purple, cyan], init_pts = [(0, 0)]*6) + cc = ColorChooser(attempts = 100, init_colors = [red, green, blue, yellow, purple, cyan], init_pts = [(0, 0)]*6) + do_with_seed(lambda : [cc.choose((0,0)) for x in range(n)]) + return cc.colors[:n] + +def make(w, h, fill = (0,0,0)): + return np.uint8(np.tile([[fill]], (h, w, 1))) + +def rgb_from_gray(img, copy = True, remove_alpha = True): + if img.ndim == 3 and img.shape[2] == 3: + return img.copy() if copy else img + elif img.ndim == 3 and img.shape[2] == 4: + return (img.copy() if copy else img)[..., :3] + elif img.ndim == 3 and img.shape[2] == 1: + return np.tile(img, (1,1,3)) + elif img.ndim == 2: + return np.tile(img[:,:,np.newaxis], (1,1,3)) + else: + raise RuntimeError('Cannot convert to rgb. Shape: ' + str(img.shape)) + +def hstack_ims(ims, bg_color = (0, 0, 0)): + max_h = max([im.shape[0] for im in ims]) + result = [] + for im in ims: + #frame = np.zeros((max_h, im.shape[1], 3)) + frame = make(im.shape[1], max_h, bg_color) + frame[:im.shape[0],:im.shape[1]] = rgb_from_gray(im) + result.append(frame) + return np.hstack(result) + +def gen_ranked_prob_map(prob_map): + prob_ranked = torch.zeros_like(prob_map) + _, index = torch.topk(prob_map, len(prob_map), largest=False) + prob_ranked[index] = torch.arange(len(prob_map)).float().cuda() + prob_ranked = prob_ranked.float() / torch.max(prob_ranked) + return prob_ranked + +def get_topk_patch_mask(prob_map): + # _, index = + pass + +def load_img(frame_path): + image = Image.open(frame_path).convert('RGB') + image = image.resize((256, 256), resample=PIL.Image.BILINEAR) + image = np.array(image) + + img_h = image.shape[0] + img_w = image.shape[1] + + return image, img_h, img_w + +def plt_subp_show_img(fig, img, cols, rows, subp_index, interpolation='bilinear', aspect='auto'): + fig.add_subplot(rows, cols, subp_index) + plt.cla() + plt.axis('off') + plt.imshow(img, interpolation=interpolation, aspect=aspect) + return fig + + + \ No newline at end of file diff --git a/foleycrafter/models/specvqgan/onset_baseline/webify.py b/foleycrafter/models/specvqgan/onset_baseline/webify.py new file mode 100644 index 0000000000000000000000000000000000000000..67bbf6399015e362e74a30003a74bf9e3f9f7c3a --- /dev/null +++ b/foleycrafter/models/specvqgan/onset_baseline/webify.py @@ -0,0 +1,241 @@ +import os +import datetime +import sys +import shutil +import glob +import argparse + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('--path', type=str) + parser.add_argument('--imgsize', type=int, default=100) + parser.add_argument('--num', type=int, default=10000) + + args = parser.parse_args() + return args + + +# -------------------------------------- joint ----------------------------------- # +def create_audio_visual_sec(args, f, name): + dir_list = [name for name in os.listdir( + args.path) if os.path.isdir(os.path.join(args.path, name))] + dir_list.sort() + + f.write('''
''') + + joint_sec = """ +

{}

+ + + + + """.format(name) + for name in dir_list: + joint_sec += '''\n'''.format(name) + joint_sec += '''\n\n''' + f.write(joint_sec) + + item_list = [] + count = [] + for i in range(len(dir_list)): + file_list = os.listdir(os.path.join(args.path, dir_list[i])) + file_list.sort() + count.append(len(file_list)) + item_list.append(file_list) + file_count = min(count) + for j in range(min(file_count, args.num)): + f.write('''\n''') + for i in range(-1, len(dir_list)): + if i == -1: + f.write(''''''.format(str(j))) + f.write('\n') + else: + sample = os.path.join(dir_list[i], item_list[i][j]) + if sample.split('.')[-1] in ['wav', 'mp3']: + f.write(''' '''.format( + sample, sample.split('.')[-1])) + elif sample.split('.')[-1] in ['jpg', 'png', 'gif']: + f.write( + ''' '''.format(sample, args.imgsize)) + elif sample.split('.')[-1] in ['mp4', 'avi', 'webm']: + f.write(''' '''.format( + sample, sample, sample.split('.')[-1], sample)) + f.write('\n') + # + + f.write('''\n''') + + f.write('''
Index #{}
sample #{}

Speed:

\n''') + f.write('''
\n''') + + +# -------------------------------------- Audio ----------------------------------- # +def create_audio_sec(args, f, name): + f.write('''
''') + + audio_sec = """ +

{}

+ + + + + + + + + + + + +\n + """.format(name) + f.write(audio_sec) + folder_path = os.path.join(args.path, 'audio') + dir_list = os.listdir(folder_path) + dir_list.sort() + audio_list = [] + for i in range(len(dir_list)): + l = os.listdir(os.path.join(folder_path, dir_list[i])) + l.sort() + audio_list.append(l) + + for j in range(len(audio_list[0])): + f.write('''\n''') + for i in range(-1, len(dir_list)): + if i == -1: + f.write(''''''.format(str(j))) + f.write('\n') + else: + audio_path = os.path.join( + folder_path, dir_list[i], audio_list[i][j]) + f.write(''' '''.format( + audio_path, audio_path.split('.')[-1])) + f.write('\n') + f.write('''\n''') + + f.write('''
Index #MixtureOriginal audio #1Original audio #2Separated audio #1Separated audio #2regenerated audio mixregenerated audio #1regenerated audio #2
audio #{}
\n''') + f.write('''
\n''') + + +# -------------------------------------- Image ----------------------------------- # +def create_image_sec(args, f, name): + f.write('''
''') + + image_sec = """ +

{}

+ + + + + + + + + +\n + """.format(name) + + f.write(image_sec) + folder_path = os.path.join(args.path, 'spec_img') + dir_list = os.listdir(folder_path) + dir_list.sort() + image_list = [] + for i in range(len(dir_list)): + l = os.listdir(os.path.join(folder_path, dir_list[i])) + l.sort() + image_list.append(l) + + for j in range(len(image_list[0])): + f.write('''\n''') + for i in range(-1, len(dir_list)): + if i == -1: + f.write(''''''.format(str(j))) + f.write('\n') + else: + img_path = os.path.join( + folder_path, dir_list[i], image_list[i][j]) + f.write(''' '''.format( + img_path, 175)) + f.write('\n') + f.write('''\n''') + + f.write('''
Index #Mixture Spec Original Spec #1Original Spec #2Separated Spec #1Separated Spec #2
audio #{}
\n''') + f.write('''
\n''') + +# -------------------------------------- Video ----------------------------------- # + + +def create_video_sec(args, f, name): + f.write('''
''') + + video_sec = """ +

{}

+ + + + + + +\n + """.format(name) + + f.write(video_sec) + # folder_path = os.path.join(args.path, 'videos') + video_list = glob.glob('%s/*.mp4' % args.path) + video_list.sort() + + columns = 3 + rows = len(video_list) // columns + 1 + + for i in range(rows): + f.write('''\n''') + for j in range(columns): + index = i * columns + j + if index < len(video_list): + video_path = video_list[i * columns + j] + f.write(''' '''.format( + video_path.split('/')[-1], video_path, video_path.split('.')[-1])) + f.write('\n') + + f.write('''\n''') + + f.write('''

{}

\n''') + f.write('''
\n''') + + +def webify(args): + html_file = os.path.join(args.path, 'index.html') + f = open(html_file, 'wt') + + # head + # + head = """ + + +Listening and Looking - UM Owens Lab + + """ + f.write(head) + + intro_sec = ''' + +

Listening and Looking - UM Owens Lab

+
Creator: Ziyang Chen
+University of Michigan
+

This page contains the results of experiment.

+''' + f.write(intro_sec) + # create_audio_sec(args, f, "Audio Separation") + # create_image_sec(args, f, 'Spectorgram Visualization') + # create_video_sec(args, f, 'CAM Visualization') + create_audio_visual_sec(args, f, 'Stereo CRW') + f.write('''\n''') + f.write('''\n''') + f.close() + + +if __name__ == "__main__": + args = parse_args() + webify(args) + print('Webify Succeed!') diff --git a/foleycrafter/models/specvqgan/util.py b/foleycrafter/models/specvqgan/util.py new file mode 100644 index 0000000000000000000000000000000000000000..deb92db0bd3157ffe72bab1ea909a14eceea8694 --- /dev/null +++ b/foleycrafter/models/specvqgan/util.py @@ -0,0 +1,150 @@ +import hashlib +import os + +import requests +from tqdm import tqdm + +URL_MAP = { + 'vggishish_lpaps': 'https://a3s.fi/swift/v1/AUTH_a235c0f452d648828f745589cde1219a/specvqgan_public/vggishish16.pt', + 'vggishish_mean_std_melspec_10s_22050hz': 'https://a3s.fi/swift/v1/AUTH_a235c0f452d648828f745589cde1219a/specvqgan_public/train_means_stds_melspec_10s_22050hz.txt', + 'melception': 'https://a3s.fi/swift/v1/AUTH_a235c0f452d648828f745589cde1219a/specvqgan_public/melception-21-05-10T09-28-40.pt', +} + +CKPT_MAP = { + 'vggishish_lpaps': 'vggishish16.pt', + 'vggishish_mean_std_melspec_10s_22050hz': 'train_means_stds_melspec_10s_22050hz.txt', + 'melception': 'melception-21-05-10T09-28-40.pt', +} + +MD5_MAP = { + 'vggishish_lpaps': '197040c524a07ccacf7715d7080a80bd', + 'vggishish_mean_std_melspec_10s_22050hz': 'f449c6fd0e248936c16f6d22492bb625', + 'melception': 'a71a41041e945b457c7d3d814bbcf72d', +} + + +def download(url, local_path, chunk_size=1024): + os.makedirs(os.path.split(local_path)[0], exist_ok=True) + with requests.get(url, stream=True) as r: + total_size = int(r.headers.get("content-length", 0)) + with tqdm(total=total_size, unit="B", unit_scale=True) as pbar: + with open(local_path, "wb") as f: + for data in r.iter_content(chunk_size=chunk_size): + if data: + f.write(data) + pbar.update(chunk_size) + + +def md5_hash(path): + with open(path, "rb") as f: + content = f.read() + return hashlib.md5(content).hexdigest() + + +def get_ckpt_path(name, root, check=False): + assert name in URL_MAP + path = os.path.join(root, CKPT_MAP[name]) + if not os.path.exists(path) or (check and not md5_hash(path) == MD5_MAP[name]): + print("Downloading {} model from {} to {}".format(name, URL_MAP[name], path)) + download(URL_MAP[name], path) + md5 = md5_hash(path) + assert md5 == MD5_MAP[name], md5 + return path + + +class KeyNotFoundError(Exception): + def __init__(self, cause, keys=None, visited=None): + self.cause = cause + self.keys = keys + self.visited = visited + messages = list() + if keys is not None: + messages.append("Key not found: {}".format(keys)) + if visited is not None: + messages.append("Visited: {}".format(visited)) + messages.append("Cause:\n{}".format(cause)) + message = "\n".join(messages) + super().__init__(message) + + +def retrieve( + list_or_dict, key, splitval="/", default=None, expand=True, pass_success=False +): + """Given a nested list or dict return the desired value at key expanding + callable nodes if necessary and :attr:`expand` is ``True``. The expansion + is done in-place. + + Parameters + ---------- + list_or_dict : list or dict + Possibly nested list or dictionary. + key : str + key/to/value, path like string describing all keys necessary to + consider to get to the desired value. List indices can also be + passed here. + splitval : str + String that defines the delimiter between keys of the + different depth levels in `key`. + default : obj + Value returned if :attr:`key` is not found. + expand : bool + Whether to expand callable nodes on the path or not. + + Returns + ------- + The desired value or if :attr:`default` is not ``None`` and the + :attr:`key` is not found returns ``default``. + + Raises + ------ + Exception if ``key`` not in ``list_or_dict`` and :attr:`default` is + ``None``. + """ + + keys = key.split(splitval) + + success = True + try: + visited = [] + parent = None + last_key = None + for key in keys: + if callable(list_or_dict): + if not expand: + raise KeyNotFoundError( + ValueError( + "Trying to get past callable node with expand=False." + ), + keys=keys, + visited=visited, + ) + list_or_dict = list_or_dict() + parent[last_key] = list_or_dict + + last_key = key + parent = list_or_dict + + try: + if isinstance(list_or_dict, dict): + list_or_dict = list_or_dict[key] + else: + list_or_dict = list_or_dict[int(key)] + except (KeyError, IndexError, ValueError) as e: + raise KeyNotFoundError(e, keys=keys, visited=visited) + + visited += [key] + # final expansion of retrieved value + if expand and callable(list_or_dict): + list_or_dict = list_or_dict() + parent[last_key] = list_or_dict + except KeyNotFoundError as e: + if default is None: + raise e + else: + list_or_dict = default + success = False + + if not pass_success: + return list_or_dict + else: + return list_or_dict, success diff --git a/foleycrafter/models/time_detector/model.py b/foleycrafter/models/time_detector/model.py new file mode 100644 index 0000000000000000000000000000000000000000..78c97ed083ebde61e6173739f7b2a567bc8a0f3f --- /dev/null +++ b/foleycrafter/models/time_detector/model.py @@ -0,0 +1,16 @@ +import torch +import torch.nn as nn +from foleycrafter.models.specvqgan.onset_baseline.models import VideoOnsetNet + +class TimeDetector(nn.Module): + def __init__(self, video_length=150, audio_length=1024): + super(TimeDetector, self).__init__() + self.pred_net = VideoOnsetNet(pretrained=False) + self.soft_fn = nn.Tanh() + self.up_sampler = nn.Linear(video_length, audio_length) + + def forward(self, inputs): + x = self.pred_net(inputs) + x = self.up_sampler(x) + x = self.soft_fn(x) + return x \ No newline at end of file diff --git a/foleycrafter/models/time_detector/resnet.py b/foleycrafter/models/time_detector/resnet.py new file mode 100644 index 0000000000000000000000000000000000000000..07a01f25a886c9e8e32bc6e4833c284d302bc050 --- /dev/null +++ b/foleycrafter/models/time_detector/resnet.py @@ -0,0 +1,347 @@ +import torch.nn as nn + +from torch.hub import load_state_dict_from_url + + +__all__ = ['r3d_18', 'mc3_18', 'r2plus1d_18'] + +model_urls = { + 'r3d_18': 'https://download.pytorch.org/models/r3d_18-b3b3357e.pth', + 'mc3_18': 'https://download.pytorch.org/models/mc3_18-a90a0ba3.pth', + 'r2plus1d_18': 'https://download.pytorch.org/models/r2plus1d_18-91a641e6.pth', +} + + +class Conv3DSimple(nn.Conv3d): + def __init__(self, + in_planes, + out_planes, + midplanes=None, + stride=1, + padding=1): + + super(Conv3DSimple, self).__init__( + in_channels=in_planes, + out_channels=out_planes, + kernel_size=(3, 3, 3), + stride=stride, + padding=padding, + bias=False) + + @staticmethod + def get_downsample_stride(stride): + return stride, stride, stride + + +class Conv2Plus1D(nn.Sequential): + + def __init__(self, + in_planes, + out_planes, + midplanes, + stride=1, + padding=1): + super(Conv2Plus1D, self).__init__( + nn.Conv3d(in_planes, midplanes, kernel_size=(1, 3, 3), + stride=(1, stride, stride), padding=(0, padding, padding), + bias=False), + nn.BatchNorm3d(midplanes), + nn.ReLU(inplace=True), + nn.Conv3d(midplanes, out_planes, kernel_size=(3, 1, 1), + stride=(stride, 1, 1), padding=(padding, 0, 0), + bias=False)) + + @staticmethod + def get_downsample_stride(stride): + return stride, stride, stride + + +class Conv3DNoTemporal(nn.Conv3d): + + def __init__(self, + in_planes, + out_planes, + midplanes=None, + stride=1, + padding=1): + + super(Conv3DNoTemporal, self).__init__( + in_channels=in_planes, + out_channels=out_planes, + kernel_size=(1, 3, 3), + stride=(1, stride, stride), + padding=(0, padding, padding), + bias=False) + + @staticmethod + def get_downsample_stride(stride): + return 1, stride, stride + + +class BasicBlock(nn.Module): + + expansion = 1 + + def __init__(self, inplanes, planes, conv_builder, stride=1, downsample=None): + midplanes = (inplanes * planes * 3 * 3 * + 3) // (inplanes * 3 * 3 + 3 * planes) + + super(BasicBlock, self).__init__() + self.conv1 = nn.Sequential( + conv_builder(inplanes, planes, midplanes, stride), + nn.BatchNorm3d(planes), + nn.ReLU(inplace=True) + ) + self.conv2 = nn.Sequential( + conv_builder(planes, planes, midplanes), + nn.BatchNorm3d(planes) + ) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.conv2(out) + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, conv_builder, stride=1, downsample=None): + + super(Bottleneck, self).__init__() + midplanes = (inplanes * planes * 3 * 3 * + 3) // (inplanes * 3 * 3 + 3 * planes) + + # 1x1x1 + self.conv1 = nn.Sequential( + nn.Conv3d(inplanes, planes, kernel_size=1, bias=False), + nn.BatchNorm3d(planes), + nn.ReLU(inplace=True) + ) + # Second kernel + self.conv2 = nn.Sequential( + conv_builder(planes, planes, midplanes, stride), + nn.BatchNorm3d(planes), + nn.ReLU(inplace=True) + ) + + # 1x1x1 + self.conv3 = nn.Sequential( + nn.Conv3d(planes, planes * self.expansion, + kernel_size=1, bias=False), + nn.BatchNorm3d(planes * self.expansion) + ) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.conv2(out) + out = self.conv3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class BasicStem(nn.Sequential): + """The default conv-batchnorm-relu stem + """ + + def __init__(self): + super(BasicStem, self).__init__( + nn.Conv3d(3, 64, kernel_size=(3, 7, 7), stride=(1, 2, 2), + padding=(1, 3, 3), bias=False), + nn.BatchNorm3d(64), + nn.ReLU(inplace=True)) + + +class R2Plus1dStem(nn.Sequential): + """R(2+1)D stem is different than the default one as it uses separated 3D convolution + """ + + def __init__(self): + super(R2Plus1dStem, self).__init__( + nn.Conv3d(3, 45, kernel_size=(1, 7, 7), + stride=(1, 2, 2), padding=(0, 3, 3), + bias=False), + nn.BatchNorm3d(45), + nn.ReLU(inplace=True), + nn.Conv3d(45, 64, kernel_size=(3, 1, 1), + stride=(1, 1, 1), padding=(1, 0, 0), + bias=False), + nn.BatchNorm3d(64), + nn.ReLU(inplace=True)) + + +class VideoResNet(nn.Module): + + def __init__(self, block, conv_makers, layers, + stem, num_classes=400, + zero_init_residual=False): + """Generic resnet video generator. + Args: + block (nn.Module): resnet building block + conv_makers (list(functions)): generator function for each layer + layers (List[int]): number of blocks per layer + stem (nn.Module, optional): Resnet stem, if None, defaults to conv-bn-relu. Defaults to None. + num_classes (int, optional): Dimension of the final FC layer. Defaults to 400. + zero_init_residual (bool, optional): Zero init bottleneck residual BN. Defaults to False. + """ + super(VideoResNet, self).__init__() + self.inplanes = 64 + + self.stem = stem() + + self.layer1 = self._make_layer( + block, conv_makers[0], 64, layers[0], stride=1) + self.layer2 = self._make_layer( + block, conv_makers[1], 128, layers[1], stride=2) + self.layer3 = self._make_layer( + block, conv_makers[2], 256, layers[2], stride=2) + self.layer4 = self._make_layer( + block, conv_makers[3], 512, layers[3], stride=2) + + self.avgpool = nn.AdaptiveAvgPool3d((1, 1, 1)) + self.fc = nn.Linear(512 * block.expansion, num_classes) + + # init weights + self._initialize_weights() + + if zero_init_residual: + for m in self.modules(): + if isinstance(m, Bottleneck): + nn.init.constant_(m.bn3.weight, 0) + + def forward(self, x): + x = self.stem(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + # Flatten the layer to fc + # x = x.flatten(1) + # x = self.fc(x) + N = x.shape[0] + x = x.squeeze() + if N == 1: + x = x[None] + + return x + + def _make_layer(self, block, conv_builder, planes, blocks, stride=1): + downsample = None + + if stride != 1 or self.inplanes != planes * block.expansion: + ds_stride = conv_builder.get_downsample_stride(stride) + downsample = nn.Sequential( + nn.Conv3d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=ds_stride, bias=False), + nn.BatchNorm3d(planes * block.expansion) + ) + layers = [] + layers.append(block(self.inplanes, planes, + conv_builder, stride, downsample)) + + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes, conv_builder)) + + return nn.Sequential(*layers) + + def _initialize_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv3d): + nn.init.kaiming_normal_(m.weight, mode='fan_out', + nonlinearity='relu') + if m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm3d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.constant_(m.bias, 0) + + +def _video_resnet(arch, pretrained=False, progress=True, **kwargs): + model = VideoResNet(**kwargs) + + if pretrained: + state_dict = load_state_dict_from_url(model_urls[arch], + progress=progress) + model.load_state_dict(state_dict) + return model + + +def r3d_18(pretrained=False, progress=True, **kwargs): + """Construct 18 layer Resnet3D model as in + https://arxiv.org/abs/1711.11248 + Args: + pretrained (bool): If True, returns a model pre-trained on Kinetics-400 + progress (bool): If True, displays a progress bar of the download to stderr + Returns: + nn.Module: R3D-18 network + """ + + return _video_resnet('r3d_18', + pretrained, progress, + block=BasicBlock, + conv_makers=[Conv3DSimple] * 4, + layers=[2, 2, 2, 2], + stem=BasicStem, **kwargs) + + +def mc3_18(pretrained=False, progress=True, **kwargs): + """Constructor for 18 layer Mixed Convolution network as in + https://arxiv.org/abs/1711.11248 + Args: + pretrained (bool): If True, returns a model pre-trained on Kinetics-400 + progress (bool): If True, displays a progress bar of the download to stderr + Returns: + nn.Module: MC3 Network definition + """ + return _video_resnet('mc3_18', + pretrained, progress, + block=BasicBlock, + conv_makers=[Conv3DSimple] + [Conv3DNoTemporal] * 3, + layers=[2, 2, 2, 2], + stem=BasicStem, **kwargs) + + +def r2plus1d_18(pretrained=False, progress=True, **kwargs): + """Constructor for the 18 layer deep R(2+1)D network as in + https://arxiv.org/abs/1711.11248 + Args: + pretrained (bool): If True, returns a model pre-trained on Kinetics-400 + progress (bool): If True, displays a progress bar of the download to stderr + Returns: + nn.Module: R(2+1)D-18 network + """ + return _video_resnet('r2plus1d_18', + pretrained, progress, + block=BasicBlock, + conv_makers=[Conv2Plus1D] * 4, + layers=[2, 2, 2, 2], + stem=R2Plus1dStem, **kwargs) \ No newline at end of file diff --git a/foleycrafter/pipelines/auffusion_pipeline.py b/foleycrafter/pipelines/auffusion_pipeline.py new file mode 100644 index 0000000000000000000000000000000000000000..5cdaf10e60f53cee3cc86a0103b97560c5aa84bb --- /dev/null +++ b/foleycrafter/pipelines/auffusion_pipeline.py @@ -0,0 +1,2103 @@ +# Copyright 2023 The HuggingFace 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 +# limitations under the License. + +import inspect +import warnings +from typing import Any, Callable, Dict, List, Optional, Union +from dataclasses import dataclass + +import torch +from packaging import version +from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer, CLIPVisionModelWithProjection + +from diffusers.configuration_utils import FrozenDict +from diffusers.utils.torch_utils import randn_tensor +from diffusers.image_processor import VaeImageProcessor +from diffusers.loaders import FromSingleFileMixin, LoraLoaderMixin, TextualInversionLoaderMixin +from diffusers.models import AutoencoderKL, ImageProjection +from diffusers.schedulers import KarrasDiffusionSchedulers +from diffusers.image_processor import PipelineImageInput +from diffusers.models.attention_processor import FusedAttnProcessor2_0 +from diffusers.utils import ( + deprecate, + is_accelerate_available, + is_accelerate_version, + logging, + replace_example_docstring, +) +from diffusers.pipelines.pipeline_utils import DiffusionPipeline +from diffusers.pipelines.stable_diffusion import StableDiffusionPipelineOutput +from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker +from huggingface_hub import snapshot_download +from diffusers import AutoencoderKL, DDPMScheduler, PNDMScheduler +from transformers import PretrainedConfig, AutoTokenizer +import torch.nn as nn +import os, json, PIL +import numpy as np +import torch.nn.functional as F +from torch.nn import Conv1d, ConvTranspose1d, AvgPool1d, Conv2d +from torch.nn.utils import weight_norm, remove_weight_norm, spectral_norm +from diffusers.utils.outputs import BaseOutput +import matplotlib.pyplot as plt + +from foleycrafter.models.auffusion_unet import UNet2DConditionModel +from foleycrafter.models.adapters.ip_adapter import VideoProjModel +from foleycrafter.models.auffusion.loaders.ip_adapter import IPAdapterMixin + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + + +def json_dump(data_json, json_save_path): + with open(json_save_path, 'w') as f: + json.dump(data_json, f, indent=4) + f.close() + + +def json_load(json_path): + with open(json_path, 'r') as f: + data = json.load(f) + f.close() + return data + + +def import_model_class_from_model_name_or_path(pretrained_model_name_or_path: str): + text_encoder_config = PretrainedConfig.from_pretrained( + pretrained_model_name_or_path + ) + model_class = text_encoder_config.architectures[0] + + if model_class == "CLIPTextModel": + from transformers import CLIPTextModel + return CLIPTextModel + if "t5" in model_class.lower(): + from transformers import T5EncoderModel + return T5EncoderModel + if "clap" in model_class.lower(): + from transformers import ClapTextModelWithProjection + return ClapTextModelWithProjection + else: + raise ValueError(f"{model_class} is not supported.") + + +class ConditionAdapter(nn.Module): + def __init__(self, config): + super(ConditionAdapter, self).__init__() + self.config = config + self.proj = nn.Linear(self.config["condition_dim"], self.config["cross_attention_dim"]) + self.norm = torch.nn.LayerNorm(self.config["cross_attention_dim"]) + print(f"INITIATED: ConditionAdapter: {self.config}") + + def forward(self, x): + x = self.proj(x) + x = self.norm(x) + return x + + @classmethod + def from_pretrained(cls, pretrained_model_name_or_path): + config_path = os.path.join(pretrained_model_name_or_path, "config.json") + ckpt_path = os.path.join(pretrained_model_name_or_path, "condition_adapter.pt") + config = json.loads(open(config_path).read()) + instance = cls(config) + instance.load_state_dict(torch.load(ckpt_path)) + print(f"LOADED: ConditionAdapter from {pretrained_model_name_or_path}") + return instance + + def save_pretrained(self, pretrained_model_name_or_path): + os.makedirs(pretrained_model_name_or_path, exist_ok=True) + config_path = os.path.join(pretrained_model_name_or_path, "config.json") + ckpt_path = os.path.join(pretrained_model_name_or_path, "condition_adapter.pt") + json_dump(self.config, config_path) + torch.save(self.state_dict(), ckpt_path) + print(f"SAVED: ConditionAdapter {self.config['model_name']} to {pretrained_model_name_or_path}") + + +def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0): + """ + Rescale `noise_cfg` according to `guidance_rescale`. Based on findings of [Common Diffusion Noise Schedules and + Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf). See Section 3.4 + """ + std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True) + std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True) + # rescale the results from guidance (fixes overexposure) + noise_pred_rescaled = noise_cfg * (std_text / std_cfg) + # mix with the original results from guidance by factor guidance_rescale to avoid "plain looking" images + noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg + return noise_cfg + + + +LRELU_SLOPE = 0.1 +MAX_WAV_VALUE = 32768.0 + + +class AttrDict(dict): + def __init__(self, *args, **kwargs): + super(AttrDict, self).__init__(*args, **kwargs) + self.__dict__ = self + + +def get_config(config_path): + config = json.loads(open(config_path).read()) + config = AttrDict(config) + return config + +def init_weights(m, mean=0.0, std=0.01): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + m.weight.data.normal_(mean, std) + + +def apply_weight_norm(m): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + weight_norm(m) + + +def get_padding(kernel_size, dilation=1): + return int((kernel_size*dilation - dilation)/2) + + +class ResBlock1(torch.nn.Module): + def __init__(self, h, channels, kernel_size=3, dilation=(1, 3, 5)): + super(ResBlock1, self).__init__() + self.h = h + self.convs1 = nn.ModuleList([ + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=dilation[0], + padding=get_padding(kernel_size, dilation[0]))), + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=dilation[1], + padding=get_padding(kernel_size, dilation[1]))), + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=dilation[2], + padding=get_padding(kernel_size, dilation[2]))) + ]) + self.convs1.apply(init_weights) + + self.convs2 = nn.ModuleList([ + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=1, + padding=get_padding(kernel_size, 1))), + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=1, + padding=get_padding(kernel_size, 1))), + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=1, + padding=get_padding(kernel_size, 1))) + ]) + self.convs2.apply(init_weights) + + def forward(self, x): + for c1, c2 in zip(self.convs1, self.convs2): + xt = F.leaky_relu(x, LRELU_SLOPE) + xt = c1(xt) + xt = F.leaky_relu(xt, LRELU_SLOPE) + xt = c2(xt) + x = xt + x + return x + + def remove_weight_norm(self): + for l in self.convs1: + remove_weight_norm(l) + for l in self.convs2: + remove_weight_norm(l) + + +class ResBlock2(torch.nn.Module): + def __init__(self, h, channels, kernel_size=3, dilation=(1, 3)): + super(ResBlock2, self).__init__() + self.h = h + self.convs = nn.ModuleList([ + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=dilation[0], + padding=get_padding(kernel_size, dilation[0]))), + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=dilation[1], + padding=get_padding(kernel_size, dilation[1]))) + ]) + self.convs.apply(init_weights) + + def forward(self, x): + for c in self.convs: + xt = F.leaky_relu(x, LRELU_SLOPE) + xt = c(xt) + x = xt + x + return x + + def remove_weight_norm(self): + for l in self.convs: + remove_weight_norm(l) + + + +class Generator(torch.nn.Module): + def __init__(self, h): + super(Generator, self).__init__() + self.h = h + self.num_kernels = len(h.resblock_kernel_sizes) + self.num_upsamples = len(h.upsample_rates) + # self.conv_pre = weight_norm(Conv1d(80, h.upsample_initial_channel, 7, 1, padding=3)) + self.conv_pre = weight_norm(Conv1d(h.num_mels, h.upsample_initial_channel, 7, 1, padding=3)) # change: 80 --> 512 + resblock = ResBlock1 if h.resblock == '1' else ResBlock2 + + self._device = "cuda" if torch.cuda.is_available() else "cpu" + + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(h.upsample_rates, h.upsample_kernel_sizes)): + if (k-u) % 2 == 0: + self.ups.append(weight_norm( + ConvTranspose1d(h.upsample_initial_channel//(2**i), h.upsample_initial_channel//(2**(i+1)), + k, u, padding=(k-u)//2))) + else: + self.ups.append(weight_norm( + ConvTranspose1d(h.upsample_initial_channel//(2**i), h.upsample_initial_channel//(2**(i+1)), + k, u, padding=(k-u)//2+1, output_padding=1))) + + # self.ups.append(weight_norm( + # ConvTranspose1d(h.upsample_initial_channel//(2**i), h.upsample_initial_channel//(2**(i+1)), + # k, u, padding=(k-u)//2))) + + + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = h.upsample_initial_channel//(2**(i+1)) + for j, (k, d) in enumerate(zip(h.resblock_kernel_sizes, h.resblock_dilation_sizes)): + self.resblocks.append(resblock(h, ch, k, d)) + + self.conv_post = weight_norm(Conv1d(ch, 1, 7, 1, padding=3)) + self.ups.apply(init_weights) + self.conv_post.apply(init_weights) + + @property + def device(self) -> torch.device: + return torch.device(self._device) + + @property + def dtype(self): + return self.type + + def forward(self, x): + x = self.conv_pre(x) + for i in range(self.num_upsamples): + x = F.leaky_relu(x, LRELU_SLOPE) + x = self.ups[i](x) + xs = None + for j in range(self.num_kernels): + if xs is None: + xs = self.resblocks[i*self.num_kernels+j](x) + else: + xs += self.resblocks[i*self.num_kernels+j](x) + x = xs / self.num_kernels + x = F.leaky_relu(x) + x = self.conv_post(x) + x = torch.tanh(x) + + return x + + def remove_weight_norm(self): + print('Removing weight norm...') + for l in self.ups: + remove_weight_norm(l) + for l in self.resblocks: + l.remove_weight_norm() + remove_weight_norm(self.conv_pre) + remove_weight_norm(self.conv_post) + + @classmethod + def from_pretrained(cls, pretrained_model_name_or_path, subfolder=None): + if subfolder is not None: + pretrained_model_name_or_path = os.path.join(pretrained_model_name_or_path, subfolder) + config_path = os.path.join(pretrained_model_name_or_path, "config.json") + ckpt_path = os.path.join(pretrained_model_name_or_path, "vocoder.pt") + + config = get_config(config_path) + vocoder = cls(config) + + state_dict_g = torch.load(ckpt_path) + vocoder.load_state_dict(state_dict_g["generator"]) + vocoder.eval() + vocoder.remove_weight_norm() + return vocoder + + @torch.no_grad() + def inference(self, mels, lengths=None): + self.eval() + with torch.no_grad(): + wavs = self(mels).squeeze(1) + + wavs = (wavs.cpu().numpy() * MAX_WAV_VALUE).astype("int16") + + if lengths is not None: + wavs = wavs[:, :lengths] + + return wavs + + + +def normalize_spectrogram( + spectrogram: torch.Tensor, + max_value: float = 200, + min_value: float = 1e-5, + power: float = 1., +) -> torch.Tensor: + + # Rescale to 0-1 + max_value = np.log(max_value) # 5.298317366548036 + min_value = np.log(min_value) # -11.512925464970229 + spectrogram = torch.clamp(spectrogram, min=min_value, max=max_value) + data = (spectrogram - min_value) / (max_value - min_value) + # Apply the power curve + data = torch.pow(data, power) + # 1D -> 3D + data = data.repeat(3, 1, 1) + # Flip Y axis: image origin at the top-left corner, spectrogram origin at the bottom-left corner + data = torch.flip(data, [1]) + + return data + + +def denormalize_spectrogram( + data: torch.Tensor, + max_value: float = 200, + min_value: float = 1e-5, + power: float = 1, +) -> torch.Tensor: + + assert len(data.shape) == 3, "Expected 3 dimensions, got {}".format(len(data.shape)) + + max_value = np.log(max_value) + min_value = np.log(min_value) + # Flip Y axis: image origin at the top-left corner, spectrogram origin at the bottom-left corner + data = torch.flip(data, [1]) + if data.shape[0] == 1: + data = data.repeat(3, 1, 1) + assert data.shape[0] == 3, "Expected 3 channels, got {}".format(data.shape[0]) + data = data[0] + # Reverse the power curve + data = torch.pow(data, 1 / power) + # Rescale to max value + spectrogram = data * (max_value - min_value) + min_value + + return spectrogram + +@staticmethod +def pt_to_numpy(images: torch.FloatTensor) -> np.ndarray: + """ + Convert a PyTorch tensor to a NumPy image. + """ + images = images.cpu().permute(0, 2, 3, 1).float().numpy() + return images + +@staticmethod +def numpy_to_pil(images: np.ndarray) -> PIL.Image.Image: + """ + Convert a numpy image or a batch of images to a PIL image. + """ + if images.ndim == 3: + images = images[None, ...] + images = (images * 255).round().astype("uint8") + if images.shape[-1] == 1: + # special case for grayscale (single channel) images + pil_images = [PIL.Image.fromarray(image.squeeze(), mode="L") for image in images] + else: + pil_images = [PIL.Image.fromarray(image) for image in images] + + return pil_images + + +def image_add_color(spec_img): + cmap = plt.get_cmap('viridis') + cmap_r = cmap.reversed() + image = cmap(np.array(spec_img)[:,:,0])[:, :, :3] # 省略透明度通道 + image = (image - image.min()) / (image.max() - image.min()) + image = PIL.Image.fromarray(np.uint8(image*255)) + return image + + +@dataclass +class PipelineOutput(BaseOutput): + """ + Output class for audio pipelines. + + Args: + audios (`np.ndarray`) + List of denoised audio samples of a NumPy array of shape `(batch_size, num_channels, sample_rate)`. + """ + + images: Union[List[PIL.Image.Image], np.ndarray] + spectrograms: Union[List[np.ndarray], np.ndarray] + audios: Union[List[np.ndarray], np.ndarray] + + + +class AuffusionPipeline(DiffusionPipeline): + + r""" + Pipeline for text-to-image generation using Stable Diffusion. + + This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the + library implements for all the pipelines (such as downloading or saving, running on a particular device, etc.) + + In addition the pipeline inherits the following loading methods: + - *Textual-Inversion*: [`loaders.TextualInversionLoaderMixin.load_textual_inversion`] + - *LoRA*: [`loaders.LoraLoaderMixin.load_lora_weights`] + - *Ckpt*: [`loaders.FromSingleFileMixin.from_single_file`] + + as well as the following saving methods: + - *LoRA*: [`loaders.LoraLoaderMixin.save_lora_weights`] + + Args: + vae ([`AutoencoderKL`]): + Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations. + text_encoder ([`CLIPTextModel`]): + Frozen text-encoder. Stable Diffusion uses the text portion of + [CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel), specifically + the [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant. + tokenizer (`CLIPTokenizer`): + Tokenizer of class + [CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer). + unet ([`UNet2DConditionModel`]): Conditional U-Net architecture to denoise the encoded image latents. + scheduler ([`SchedulerMixin`]): + A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of + [`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`]. + safety_checker ([`StableDiffusionSafetyChecker`]): + Classification module that estimates whether generated images could be considered offensive or harmful. + Please, refer to the [model card](https://huggingface.co/runwayml/stable-diffusion-v1-5) for details. + feature_extractor ([`CLIPImageProcessor`]): + Model that extracts features from generated images to be used as inputs for the `safety_checker`. + """ + _optional_components = ["safety_checker", "feature_extractor", "text_encoder_list", "tokenizer_list", "adapter_list", "vocoder"] + + def __init__( + self, + vae: AutoencoderKL, + unet: UNet2DConditionModel, + scheduler: KarrasDiffusionSchedulers, + safety_checker: StableDiffusionSafetyChecker, + feature_extractor: CLIPImageProcessor, + text_encoder_list: Optional[List[Callable]] = None, + tokenizer_list: Optional[List[Callable]] = None, + vocoder: Generator = None, + requires_safety_checker: bool = False, + adapter_list: Optional[List[Callable]] = None, + tokenizer_model_max_length: Optional[int] = 77, # 77 is the default value for the CLIPTokenizer(and set for other models) + ): + super().__init__() + + self.text_encoder_list = text_encoder_list + self.tokenizer_list = tokenizer_list + self.vocoder = vocoder + self.adapter_list = adapter_list + self.tokenizer_model_max_length = tokenizer_model_max_length + + self.register_modules( + vae=vae, + unet=unet, + scheduler=scheduler, + safety_checker=safety_checker, + feature_extractor=feature_extractor, + ) + + self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) + self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor) + self.register_to_config(requires_safety_checker=requires_safety_checker) + + + @classmethod + def from_pretrained( + cls, + pretrained_model_name_or_path: str = "auffusion/auffusion-full-no-adapter", + dtype: torch.dtype = torch.float16, + device: str = "cuda", + ): + if not os.path.isdir(pretrained_model_name_or_path): + pretrained_model_name_or_path = snapshot_download(pretrained_model_name_or_path) + + vae = AutoencoderKL.from_pretrained(pretrained_model_name_or_path, subfolder="vae") + unet = UNet2DConditionModel.from_pretrained(pretrained_model_name_or_path, subfolder="unet") + feature_extractor = CLIPImageProcessor.from_pretrained(pretrained_model_name_or_path, subfolder="feature_extractor") + scheduler = PNDMScheduler.from_pretrained(pretrained_model_name_or_path, subfolder="scheduler") + + vocoder = Generator.from_pretrained(pretrained_model_name_or_path, subfolder="vocoder").to(device, dtype) + + text_encoder_list, tokenizer_list, adapter_list = [], [], [] + + condition_json_path = os.path.join(pretrained_model_name_or_path, "condition_config.json") + condition_json_list = json.loads(open(condition_json_path).read()) + + for i, condition_item in enumerate(condition_json_list): + + # Load Condition Adapter + text_encoder_path = os.path.join(pretrained_model_name_or_path, condition_item["text_encoder_name"]) + tokenizer = AutoTokenizer.from_pretrained(text_encoder_path) + tokenizer_list.append(tokenizer) + text_encoder_cls = import_model_class_from_model_name_or_path(text_encoder_path) + text_encoder = text_encoder_cls.from_pretrained(text_encoder_path).to(device, dtype) + text_encoder_list.append(text_encoder) + print(f"LOADING CONDITION ENCODER {i}") + + # Load Condition Adapter + adapter_path = os.path.join(pretrained_model_name_or_path, condition_item["condition_adapter_name"]) + adapter = ConditionAdapter.from_pretrained(adapter_path).to(device, dtype) + adapter_list.append(adapter) + print(f"LOADING CONDITION ADAPTER {i}") + + + pipeline = cls( + vae=vae, + unet=unet, + text_encoder_list=text_encoder_list, + tokenizer_list=tokenizer_list, + vocoder=vocoder, + adapter_list=adapter_list, + scheduler=scheduler, + safety_checker=None, + feature_extractor=feature_extractor, + ) + pipeline = pipeline.to(device, dtype) + + return pipeline + + + def to(self, device, dtype=None): + super().to(device, dtype) + + self.vocoder.to(device, dtype) + + for text_encoder in self.text_encoder_list: + text_encoder.to(device, dtype) + + if self.adapter_list is not None: + for adapter in self.adapter_list: + adapter.to(device, dtype) + + return self + + + def enable_vae_slicing(self): + r""" + Enable sliced VAE decoding. + + When this option is enabled, the VAE will split the input tensor in slices to compute decoding in several + steps. This is useful to save some memory and allow larger batch sizes. + """ + self.vae.enable_slicing() + + def disable_vae_slicing(self): + r""" + Disable sliced VAE decoding. If `enable_vae_slicing` was previously invoked, this method will go back to + computing decoding in one step. + """ + self.vae.disable_slicing() + + def enable_vae_tiling(self): + r""" + Enable tiled VAE decoding. + + When this option is enabled, the VAE will split the input tensor into tiles to compute decoding and encoding in + several steps. This is useful to save a large amount of memory and to allow the processing of larger images. + """ + self.vae.enable_tiling() + + def disable_vae_tiling(self): + r""" + Disable tiled VAE decoding. If `enable_vae_tiling` was previously invoked, this method will go back to + computing decoding in one step. + """ + self.vae.disable_tiling() + + def enable_sequential_cpu_offload(self, gpu_id=0): + r""" + Offloads all models to CPU using accelerate, significantly reducing memory usage. When called, unet, + text_encoder, vae and safety checker have their state dicts saved to CPU and then are moved to a + `torch.device('meta') and loaded to GPU only when their specific submodule has its `forward` method called. + Note that offloading happens on a submodule basis. Memory savings are higher than with + `enable_model_cpu_offload`, but performance is lower. + """ + if is_accelerate_available() and is_accelerate_version(">=", "0.14.0"): + from accelerate import cpu_offload + else: + raise ImportError("`enable_sequential_cpu_offload` requires `accelerate v0.14.0` or higher") + + device = torch.device(f"cuda:{gpu_id}") + + if self.device.type != "cpu": + self.to("cpu", silence_dtype_warnings=True) + torch.cuda.empty_cache() # otherwise we don't see the memory savings (but they probably exist) + + for cpu_offloaded_model in [self.unet, self.text_encoder, self.vae]: + cpu_offload(cpu_offloaded_model, device) + + if self.safety_checker is not None: + cpu_offload(self.safety_checker, execution_device=device, offload_buffers=True) + + def enable_model_cpu_offload(self, gpu_id=0): + r""" + Offloads all models to CPU using accelerate, reducing memory usage with a low impact on performance. Compared + to `enable_sequential_cpu_offload`, this method moves one whole model at a time to the GPU when its `forward` + method is called, and the model remains in GPU until the next model runs. Memory savings are lower than with + `enable_sequential_cpu_offload`, but performance is much better due to the iterative execution of the `unet`. + """ + if is_accelerate_available() and is_accelerate_version(">=", "0.17.0.dev0"): + from accelerate import cpu_offload_with_hook + else: + raise ImportError("`enable_model_cpu_offload` requires `accelerate v0.17.0` or higher.") + + device = torch.device(f"cuda:{gpu_id}") + + if self.device.type != "cpu": + self.to("cpu", silence_dtype_warnings=True) + torch.cuda.empty_cache() # otherwise we don't see the memory savings (but they probably exist) + + hook = None + for cpu_offloaded_model in [self.text_encoder, self.unet, self.vae]: + _, hook = cpu_offload_with_hook(cpu_offloaded_model, device, prev_module_hook=hook) + + if self.safety_checker is not None: + _, hook = cpu_offload_with_hook(self.safety_checker, device, prev_module_hook=hook) + + # We'll offload the last model manually. + self.final_offload_hook = hook + + @property + def _execution_device(self): + r""" + Returns the device on which the pipeline's models will be executed. After calling + `pipeline.enable_sequential_cpu_offload()` the execution device can only be inferred from Accelerate's module + hooks. + """ + if not hasattr(self.unet, "_hf_hook"): + return self.device + for module in self.unet.modules(): + if ( + hasattr(module, "_hf_hook") + and hasattr(module._hf_hook, "execution_device") + and module._hf_hook.execution_device is not None + ): + return torch.device(module._hf_hook.execution_device) + return self.device + + + def _encode_prompt( + self, + prompt, + device, + num_images_per_prompt, + do_classifier_free_guidance, + negative_prompt=None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + ): + + assert len(self.text_encoder_list) == len(self.tokenizer_list), "Number of text_encoders must match number of tokenizers" + if self.adapter_list is not None: + assert len(self.text_encoder_list) == len(self.adapter_list), "Number of text_encoders must match number of adapters" + + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + def get_prompt_embeds(prompt_list, device): + if isinstance(prompt_list, str): + prompt_list = [prompt_list] + + prompt_embeds_list = [] + for prompt in prompt_list: + encoder_hidden_states_list = [] + + # Generate condition embedding + for j in range(len(self.text_encoder_list)): + # get condition embedding using condition encoder + input_ids = self.tokenizer_list[j](prompt, return_tensors="pt").input_ids.to(device) + cond_embs = self.text_encoder_list[j](input_ids).last_hidden_state # [bz, text_len, text_dim] + # padding to max_length + if cond_embs.shape[1] < self.tokenizer_model_max_length: + cond_embs = torch.functional.F.pad(cond_embs, (0, 0, 0, self.tokenizer_model_max_length - cond_embs.shape[1]), value=0) + else: + cond_embs = cond_embs[:, :self.tokenizer_model_max_length, :] + + # use condition adapter + if self.adapter_list is not None: + cond_embs = self.adapter_list[j](cond_embs) + encoder_hidden_states_list.append(cond_embs) + + prompt_embeds = torch.cat(encoder_hidden_states_list, dim=1) + prompt_embeds_list.append(prompt_embeds) + + prompt_embeds = torch.cat(prompt_embeds_list, dim=0) + return prompt_embeds + + + if prompt_embeds is None: + prompt_embeds = get_prompt_embeds(prompt, device) + + prompt_embeds = prompt_embeds.to(dtype=self.unet.dtype, device=device) + + bs_embed, seq_len, _ = prompt_embeds.shape + # duplicate text embeddings for each generation per prompt, using mps friendly method + prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) + prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1) + + if do_classifier_free_guidance and negative_prompt_embeds is None: + + if negative_prompt is None: + negative_prompt_embeds = torch.zeros_like(prompt_embeds).to(dtype=prompt_embeds.dtype, device=device) + + elif prompt is not None and type(prompt) is not type(negative_prompt): + raise TypeError( + f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" + f" {type(prompt)}." + ) + elif isinstance(negative_prompt, str): + negative_prompt = [negative_prompt] + elif batch_size != len(negative_prompt): + raise ValueError( + f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" + f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" + " the batch size of `prompt`." + ) + else: + negative_prompt_embeds = get_prompt_embeds(negative_prompt, device) + + if do_classifier_free_guidance: + # duplicate unconditional embeddings for each generation per prompt, using mps friendly method + seq_len = negative_prompt_embeds.shape[1] + + negative_prompt_embeds = negative_prompt_embeds.to(dtype=self.unet.dtype, device=device) + + negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1) + negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) + + # For classifier free guidance, we need to do two forward passes. + # Here we concatenate the unconditional and text embeddings into a single batch + # to avoid doing two forward passes + prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds]) + + return prompt_embeds + + + def run_safety_checker(self, image, device, dtype): + if self.safety_checker is None: + has_nsfw_concept = None + else: + if torch.is_tensor(image): + feature_extractor_input = self.image_processor.postprocess(image, output_type="pil") + else: + feature_extractor_input = self.image_processor.numpy_to_pil(image) + safety_checker_input = self.feature_extractor(feature_extractor_input, return_tensors="pt").to(device) + image, has_nsfw_concept = self.safety_checker( + images=image, clip_input=safety_checker_input.pixel_values.to(dtype) + ) + return image, has_nsfw_concept + + def decode_latents(self, latents): + warnings.warn( + "The decode_latents method is deprecated and will be removed in a future version. Please" + " use VaeImageProcessor instead", + FutureWarning, + ) + latents = 1 / self.vae.config.scaling_factor * latents + image = self.vae.decode(latents, return_dict=False)[0] + image = (image / 2 + 0.5).clamp(0, 1) + # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16 + image = image.cpu().permute(0, 2, 3, 1).float().numpy() + return image + + def prepare_extra_step_kwargs(self, generator, eta): + # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature + # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers. + # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502 + # and should be between [0, 1] + + accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys()) + extra_step_kwargs = {} + if accepts_eta: + extra_step_kwargs["eta"] = eta + + # check if the scheduler accepts generator + accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys()) + if accepts_generator: + extra_step_kwargs["generator"] = generator + return extra_step_kwargs + + + def check_inputs( + self, + prompt, + height, + width, + callback_steps, + negative_prompt=None, + prompt_embeds=None, + negative_prompt_embeds=None, + ): + if height % 8 != 0 or width % 8 != 0: + raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.") + + if (callback_steps is None) or ( + callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0) + ): + raise ValueError( + f"`callback_steps` has to be a positive integer but is {callback_steps} of type" + f" {type(callback_steps)}." + ) + + if prompt is not None and prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" + " only forward one of the two." + ) + elif prompt is None and prompt_embeds is None: + raise ValueError( + "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." + ) + elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): + raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") + + if negative_prompt is not None and negative_prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:" + f" {negative_prompt_embeds}. Please make sure to only forward one of the two." + ) + + if prompt_embeds is not None and negative_prompt_embeds is not None: + if prompt_embeds.shape != negative_prompt_embeds.shape: + raise ValueError( + "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but" + f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`" + f" {negative_prompt_embeds.shape}." + ) + + + + def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None): + shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor) + if isinstance(generator, list) and len(generator) != batch_size: + raise ValueError( + f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" + f" size of {batch_size}. Make sure the batch size matches the length of the generators." + ) + + if latents is None: + latents = torch.randn(shape, generator=generator, device=device, dtype=dtype) + else: + latents = latents.to(device) + + # scale the initial noise by the standard deviation required by the scheduler + latents = latents * self.scheduler.init_noise_sigma + return latents + + @torch.no_grad() + def __call__( + self, + prompt: Union[str, List[str]] = None, + height: Optional[int] = 256, + width: Optional[int] = 1024, + num_inference_steps: int = 100, + guidance_scale: float = 7.5, + negative_prompt: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: Optional[int] = 1, + eta: float = 0.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + latents: Optional[torch.FloatTensor] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + output_type: Optional[str] = "pt", + return_dict: bool = True, + callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, + callback_steps: int = 1, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + guidance_rescale: float = 0.0, + duration: Optional[float] = 10, + ): + + # 0. Default height and width to unet + height = height or self.unet.config.sample_size * self.vae_scale_factor + width = width or self.unet.config.sample_size * self.vae_scale_factor + audio_length = int(duration * 16000) + + + # 1. Check inputs. Raise error if not correct + self.check_inputs( + prompt, height, width, callback_steps, negative_prompt, prompt_embeds, negative_prompt_embeds + ) + + + # 2. Define call parameters + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + device = self._execution_device + # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) + # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` + # corresponds to doing no classifier free guidance. + do_classifier_free_guidance = guidance_scale > 1.0 + + # 3. Encode input prompt + prompt_embeds = self._encode_prompt( + prompt, + device, + num_images_per_prompt, + do_classifier_free_guidance, + negative_prompt, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds + ) + + # 4. Prepare timesteps + self.scheduler.set_timesteps(num_inference_steps, device=device) + timesteps = self.scheduler.timesteps + + # 5. Prepare latent variables + num_channels_latents = self.unet.config.in_channels + latents = self.prepare_latents( + batch_size * num_images_per_prompt, + num_channels_latents, + height, + width, + prompt_embeds.dtype, + device, + generator, + latents, + ) + + # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline + extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) + + # 7. Denoising loop + num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + # expand the latents if we are doing classifier free guidance + latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + # predict the noise residual + noise_pred = self.unet( + latent_model_input, + t, + encoder_hidden_states=prompt_embeds, + cross_attention_kwargs=cross_attention_kwargs, + return_dict=False, + )[0] + + # perform guidance + if do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) + noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) + + if do_classifier_free_guidance and guidance_rescale > 0.0: + # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf + noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=guidance_rescale) + + # compute the previous noisy sample x_t -> x_t-1 + latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] + + # call the callback, if provided + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + if callback is not None and i % callback_steps == 0: + callback(i, t, latents) + + if not output_type == "latent": + image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0] + image, has_nsfw_concept = self.run_safety_checker(image, device, prompt_embeds.dtype) + else: + image = latents + has_nsfw_concept = None + + if has_nsfw_concept is None: + do_denormalize = [True] * image.shape[0] + else: + do_denormalize = [not has_nsfw for has_nsfw in has_nsfw_concept] + + image = self.image_processor.postprocess(image, output_type=output_type, do_denormalize=do_denormalize) + + # Offload last model to CPU + if hasattr(self, "final_offload_hook") and self.final_offload_hook is not None: + self.final_offload_hook.offload() + + + # Generate audio + spectrograms, audios = [], [] + for img in image: + spectrogram = denormalize_spectrogram(img) + audio = self.vocoder.inference(spectrogram, lengths=audio_length)[0] + audios.append(audio) + spectrograms.append(spectrogram) + + # Convert to PIL + images = pt_to_numpy(image) + images = numpy_to_pil(images) + images = [image_add_color(image) for image in images] + + if not return_dict: + return (images, audios, spectrograms) + + + return PipelineOutput(images=images, audios=audios, spectrograms=spectrograms) + +def retrieve_timesteps( + scheduler, + num_inference_steps: Optional[int] = None, + device: Optional[Union[str, torch.device]] = None, + timesteps: Optional[List[int]] = None, + **kwargs, +): + """ + Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles + custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. + + Args: + scheduler (`SchedulerMixin`): + The scheduler to get timesteps from. + num_inference_steps (`int`): + The number of diffusion steps used when generating samples with a pre-trained model. If used, + `timesteps` must be `None`. + device (`str` or `torch.device`, *optional*): + The device to which the timesteps should be moved to. If `None`, the timesteps are not moved. + timesteps (`List[int]`, *optional*): + Custom timesteps used to support arbitrary spacing between timesteps. If `None`, then the default + timestep spacing strategy of the scheduler is used. If `timesteps` is passed, `num_inference_steps` + must be `None`. + + Returns: + `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the + second element is the number of inference steps. + """ + if timesteps is not None: + accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) + if not accepts_timesteps: + raise ValueError( + f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" + f" timestep schedules. Please check whether you are using the correct scheduler." + ) + scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) + timesteps = scheduler.timesteps + num_inference_steps = len(timesteps) + else: + scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) + timesteps = scheduler.timesteps + return timesteps, num_inference_steps + +class AuffusionNoAdapterPipeline( + DiffusionPipeline, TextualInversionLoaderMixin, LoraLoaderMixin, IPAdapterMixin, FromSingleFileMixin +): + r""" + Pipeline for text-to-image generation using Stable Diffusion. + + This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods + implemented for all pipelines (downloading, saving, running on a particular device, etc.). + + The pipeline also inherits the following loading methods: + - [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] for loading textual inversion embeddings + - [`~loaders.LoraLoaderMixin.load_lora_weights`] for loading LoRA weights + - [`~loaders.LoraLoaderMixin.save_lora_weights`] for saving LoRA weights + - [`~loaders.FromSingleFileMixin.from_single_file`] for loading `.ckpt` files + - [`~loaders.IPAdapterMixin.load_ip_adapter`] for loading IP Adapters + + Args: + vae ([`AutoencoderKL`]): + Variational Auto-Encoder (VAE) model to encode and decode images to and from latent representations. + text_encoder ([`~transformers.CLIPTextModel`]): + Frozen text-encoder ([clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14)). + tokenizer ([`~transformers.CLIPTokenizer`]): + A `CLIPTokenizer` to tokenize text. + unet ([`UNet2DConditionModel`]): + A `UNet2DConditionModel` to denoise the encoded image latents. + scheduler ([`SchedulerMixin`]): + A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of + [`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`]. + safety_checker ([`StableDiffusionSafetyChecker`]): + Classification module that estimates whether generated images could be considered offensive or harmful. + Please refer to the [model card](https://huggingface.co/runwayml/stable-diffusion-v1-5) for more details + about a model's potential harms. + feature_extractor ([`~transformers.CLIPImageProcessor`]): + A `CLIPImageProcessor` to extract features from generated images; used as inputs to the `safety_checker`. + """ + + model_cpu_offload_seq = "text_encoder->image_encoder->unet->vae" + _optional_components = ["safety_checker", "feature_extractor", "image_encoder"] + _exclude_from_cpu_offload = ["safety_checker"] + _callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds"] + + def __init__( + self, + vae: AutoencoderKL, + text_encoder: CLIPTextModel, + tokenizer: CLIPTokenizer, + unet: UNet2DConditionModel, + scheduler: KarrasDiffusionSchedulers, + safety_checker: StableDiffusionSafetyChecker, + feature_extractor: CLIPImageProcessor, + image_encoder: CLIPVisionModelWithProjection = None, + requires_safety_checker: bool = True, + ): + super().__init__() + + if hasattr(scheduler.config, "steps_offset") and scheduler.config.steps_offset != 1: + deprecation_message = ( + f"The configuration file of this scheduler: {scheduler} is outdated. `steps_offset`" + f" should be set to 1 instead of {scheduler.config.steps_offset}. Please make sure " + "to update the config accordingly as leaving `steps_offset` might led to incorrect results" + " in future versions. If you have downloaded this checkpoint from the Hugging Face Hub," + " it would be very nice if you could open a Pull request for the `scheduler/scheduler_config.json`" + " file" + ) + deprecate("steps_offset!=1", "1.0.0", deprecation_message, standard_warn=False) + new_config = dict(scheduler.config) + new_config["steps_offset"] = 1 + scheduler._internal_dict = FrozenDict(new_config) + + if hasattr(scheduler.config, "clip_sample") and scheduler.config.clip_sample is True: + deprecation_message = ( + f"The configuration file of this scheduler: {scheduler} has not set the configuration `clip_sample`." + " `clip_sample` should be set to False in the configuration file. Please make sure to update the" + " config accordingly as not setting `clip_sample` in the config might lead to incorrect results in" + " future versions. If you have downloaded this checkpoint from the Hugging Face Hub, it would be very" + " nice if you could open a Pull request for the `scheduler/scheduler_config.json` file" + ) + deprecate("clip_sample not set", "1.0.0", deprecation_message, standard_warn=False) + new_config = dict(scheduler.config) + new_config["clip_sample"] = False + scheduler._internal_dict = FrozenDict(new_config) + + if safety_checker is None and requires_safety_checker: + logger.warning( + f"You have disabled the safety checker for {self.__class__} by passing `safety_checker=None`. Ensure" + " that you abide to the conditions of the Stable Diffusion license and do not expose unfiltered" + " results in services or applications open to the public. Both the diffusers team and Hugging Face" + " strongly recommend to keep the safety filter enabled in all public facing circumstances, disabling" + " it only for use-cases that involve analyzing network behavior or auditing its results. For more" + " information, please have a look at https://github.com/huggingface/diffusers/pull/254 ." + ) + + if safety_checker is not None and feature_extractor is None: + raise ValueError( + "Make sure to define a feature extractor when loading {self.__class__} if you want to use the safety" + " checker. If you do not want to use the safety checker, you can pass `'safety_checker=None'` instead." + ) + + is_unet_version_less_0_9_0 = hasattr(unet.config, "_diffusers_version") and version.parse( + version.parse(unet.config._diffusers_version).base_version + ) < version.parse("0.9.0.dev0") + is_unet_sample_size_less_64 = hasattr(unet.config, "sample_size") and unet.config.sample_size < 64 + if is_unet_version_less_0_9_0 and is_unet_sample_size_less_64: + deprecation_message = ( + "The configuration file of the unet has set the default `sample_size` to smaller than" + " 64 which seems highly unlikely. If your checkpoint is a fine-tuned version of any of the" + " following: \n- CompVis/stable-diffusion-v1-4 \n- CompVis/stable-diffusion-v1-3 \n-" + " CompVis/stable-diffusion-v1-2 \n- CompVis/stable-diffusion-v1-1 \n- runwayml/stable-diffusion-v1-5" + " \n- runwayml/stable-diffusion-inpainting \n you should change 'sample_size' to 64 in the" + " configuration file. Please make sure to update the config accordingly as leaving `sample_size=32`" + " in the config might lead to incorrect results in future versions. If you have downloaded this" + " checkpoint from the Hugging Face Hub, it would be very nice if you could open a Pull request for" + " the `unet/config.json` file" + ) + deprecate("sample_size<64", "1.0.0", deprecation_message, standard_warn=False) + new_config = dict(unet.config) + new_config["sample_size"] = 64 + unet._internal_dict = FrozenDict(new_config) + + self.register_modules( + vae=vae, + text_encoder=text_encoder, + tokenizer=tokenizer, + unet=unet, + scheduler=scheduler, + safety_checker=safety_checker, + feature_extractor=feature_extractor, + image_encoder=image_encoder, + ) + self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) + self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor) + self.register_to_config(requires_safety_checker=requires_safety_checker) + + def enable_vae_slicing(self): + r""" + Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to + compute decoding in several steps. This is useful to save some memory and allow larger batch sizes. + """ + self.vae.enable_slicing() + + def disable_vae_slicing(self): + r""" + Disable sliced VAE decoding. If `enable_vae_slicing` was previously enabled, this method will go back to + computing decoding in one step. + """ + self.vae.disable_slicing() + + def enable_vae_tiling(self): + r""" + Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to + compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow + processing larger images. + """ + self.vae.enable_tiling() + + def disable_vae_tiling(self): + r""" + Disable tiled VAE decoding. If `enable_vae_tiling` was previously enabled, this method will go back to + computing decoding in one step. + """ + self.vae.disable_tiling() + + def _encode_prompt( + self, + prompt, + device, + num_images_per_prompt, + do_classifier_free_guidance, + negative_prompt=None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + lora_scale: Optional[float] = None, + **kwargs, + ): + deprecation_message = "`_encode_prompt()` is deprecated and it will be removed in a future version. Use `encode_prompt()` instead. Also, be aware that the output format changed from a concatenated tensor to a tuple." + deprecate("_encode_prompt()", "1.0.0", deprecation_message, standard_warn=False) + + prompt_embeds_tuple = self.encode_prompt( + prompt=prompt, + device=device, + num_images_per_prompt=num_images_per_prompt, + do_classifier_free_guidance=do_classifier_free_guidance, + negative_prompt=negative_prompt, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + lora_scale=lora_scale, + **kwargs, + ) + + # concatenate for backwards comp + prompt_embeds = torch.cat([prompt_embeds_tuple[1], prompt_embeds_tuple[0]]) + + return prompt_embeds + + def encode_prompt( + self, + prompt, + device, + num_images_per_prompt, + do_classifier_free_guidance, + negative_prompt=None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + lora_scale: Optional[float] = None, + clip_skip: Optional[int] = None, + ): + r""" + Encodes the prompt into text encoder hidden states. + + Args: + prompt (`str` or `List[str]`, *optional*): + prompt to be encoded + device: (`torch.device`): + torch device + num_images_per_prompt (`int`): + number of images that should be generated per prompt + do_classifier_free_guidance (`bool`): + whether to use classifier free guidance or not + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation. If not defined, one has to pass + `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is + less than `1`). + prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not + provided, text embeddings will be generated from `prompt` input argument. + negative_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input + argument. + lora_scale (`float`, *optional*): + A LoRA scale that will be applied to all LoRA layers of the text encoder if LoRA layers are loaded. + clip_skip (`int`, *optional*): + Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that + the output of the pre-final layer will be used for computing the prompt embeddings. + """ + # set lora scale so that monkey patched LoRA + # function of text encoder can correctly access it + if lora_scale is not None and isinstance(self, LoraLoaderMixin): + self._lora_scale = lora_scale + + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + if prompt_embeds is None: + # textual inversion: procecss multi-vector tokens if necessary + if isinstance(self, TextualInversionLoaderMixin): + prompt = self.maybe_convert_prompt(prompt, self.tokenizer) + + text_inputs = self.tokenizer( + prompt, + padding="max_length", + max_length=self.tokenizer.model_max_length, + truncation=True, + return_tensors="pt", + ) + text_input_ids = text_inputs.input_ids + untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids + + if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal( + text_input_ids, untruncated_ids + ): + removed_text = self.tokenizer.batch_decode( + untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1] + ) + logger.warning( + "The following part of your input was truncated because CLIP can only handle sequences up to" + f" {self.tokenizer.model_max_length} tokens: {removed_text}" + ) + + if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask: + attention_mask = text_inputs.attention_mask.to(device) + else: + attention_mask = None + + if clip_skip is None: + prompt_embeds = self.text_encoder(text_input_ids.to(device), attention_mask=attention_mask) + prompt_embeds = prompt_embeds[0] + else: + prompt_embeds = self.text_encoder( + text_input_ids.to(device), attention_mask=attention_mask, output_hidden_states=True + ) + # Access the `hidden_states` first, that contains a tuple of + # all the hidden states from the encoder layers. Then index into + # the tuple to access the hidden states from the desired layer. + prompt_embeds = prompt_embeds[-1][-(clip_skip + 1)] + # We also need to apply the final LayerNorm here to not mess with the + # representations. The `last_hidden_states` that we typically use for + # obtaining the final prompt representations passes through the LayerNorm + # layer. + prompt_embeds = self.text_encoder.text_model.final_layer_norm(prompt_embeds) + + if self.text_encoder is not None: + prompt_embeds_dtype = self.text_encoder.dtype + elif self.unet is not None: + prompt_embeds_dtype = self.unet.dtype + else: + prompt_embeds_dtype = prompt_embeds.dtype + + prompt_embeds = prompt_embeds.to(dtype=prompt_embeds_dtype, device=device) + + bs_embed, seq_len, _ = prompt_embeds.shape + # duplicate text embeddings for each generation per prompt, using mps friendly method + prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) + prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1) + + # get unconditional embeddings for classifier free guidance + if do_classifier_free_guidance and negative_prompt_embeds is None: + uncond_tokens: List[str] + if negative_prompt is None: + uncond_tokens = [""] * batch_size + elif prompt is not None and type(prompt) is not type(negative_prompt): + raise TypeError( + f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" + f" {type(prompt)}." + ) + elif isinstance(negative_prompt, str): + uncond_tokens = [negative_prompt] + elif batch_size != len(negative_prompt): + raise ValueError( + f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" + f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" + " the batch size of `prompt`." + ) + else: + uncond_tokens = negative_prompt + + # textual inversion: procecss multi-vector tokens if necessary + if isinstance(self, TextualInversionLoaderMixin): + uncond_tokens = self.maybe_convert_prompt(uncond_tokens, self.tokenizer) + + max_length = prompt_embeds.shape[1] + uncond_input = self.tokenizer( + uncond_tokens, + padding="max_length", + max_length=max_length, + truncation=True, + return_tensors="pt", + ) + + if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask: + attention_mask = uncond_input.attention_mask.to(device) + else: + attention_mask = None + + negative_prompt_embeds = self.text_encoder( + uncond_input.input_ids.to(device), + attention_mask=attention_mask, + ) + negative_prompt_embeds = negative_prompt_embeds[0] + + if do_classifier_free_guidance: + # duplicate unconditional embeddings for each generation per prompt, using mps friendly method + seq_len = negative_prompt_embeds.shape[1] + + negative_prompt_embeds = negative_prompt_embeds.to(dtype=prompt_embeds_dtype, device=device) + + negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1) + negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) + + return prompt_embeds, negative_prompt_embeds + + def prepare_ip_adapter_image_embeds( + self, ip_adapter_image, ip_adapter_image_embeds, device, num_images_per_prompt, do_classifier_free_guidance + ): + if ip_adapter_image_embeds is None: + if not isinstance(ip_adapter_image, list): + ip_adapter_image = [ip_adapter_image] + + if len(ip_adapter_image) != len(self.unet.encoder_hid_proj.image_projection_layers): + raise ValueError( + f"`ip_adapter_image` must have same length as the number of IP Adapters. Got {len(ip_adapter_image)} images and {len(self.unet.encoder_hid_proj.image_projection_layers)} IP Adapters." + ) + + image_embeds = [] + for single_ip_adapter_image, image_proj_layer in zip( + ip_adapter_image, self.unet.encoder_hid_proj.image_projection_layers + ): + output_hidden_state = not isinstance(image_proj_layer, ImageProjection) + single_image_embeds, single_negative_image_embeds = self.encode_image( + single_ip_adapter_image, device, 1, output_hidden_state + ) + single_image_embeds = torch.stack([single_image_embeds] * num_images_per_prompt, dim=0) + single_negative_image_embeds = torch.stack( + [single_negative_image_embeds] * num_images_per_prompt, dim=0 + ) + + if do_classifier_free_guidance: + single_image_embeds = torch.cat([single_negative_image_embeds, single_image_embeds]) + single_image_embeds = single_image_embeds.to(device) + + image_embeds.append(single_image_embeds) + else: + repeat_dims = [1] + image_embeds = [] + for single_image_embeds in ip_adapter_image_embeds: + if do_classifier_free_guidance: + single_negative_image_embeds, single_image_embeds = single_image_embeds.chunk(2) + single_image_embeds = single_image_embeds.repeat( + num_images_per_prompt, *(repeat_dims * len(single_image_embeds.shape[1:])) + ) + single_negative_image_embeds = single_negative_image_embeds.repeat( + num_images_per_prompt, *(repeat_dims * len(single_negative_image_embeds.shape[1:])) + ) + single_image_embeds = torch.cat([single_negative_image_embeds, single_image_embeds]) + else: + single_image_embeds = single_image_embeds.repeat( + num_images_per_prompt, *(repeat_dims * len(single_image_embeds.shape[1:])) + ) + image_embeds.append(single_image_embeds) + + return image_embeds + + def encode_image(self, image, device, num_images_per_prompt, output_hidden_states=None): + dtype = next(self.image_encoder.parameters()).dtype + + if not isinstance(image, torch.Tensor): + image = self.feature_extractor(image, return_tensors="pt").pixel_values + + image = image.to(device=device, dtype=dtype) + if output_hidden_states: + image_enc_hidden_states = self.image_encoder(image, output_hidden_states=True).hidden_states[-2] + image_enc_hidden_states = image_enc_hidden_states.repeat_interleave(num_images_per_prompt, dim=0) + uncond_image_enc_hidden_states = self.image_encoder( + torch.zeros_like(image), output_hidden_states=True + ).hidden_states[-2] + uncond_image_enc_hidden_states = uncond_image_enc_hidden_states.repeat_interleave( + num_images_per_prompt, dim=0 + ) + return image_enc_hidden_states, uncond_image_enc_hidden_states + else: + image_embeds = self.image_encoder(image).image_embeds + image_embeds = image_embeds.repeat_interleave(num_images_per_prompt, dim=0) + uncond_image_embeds = torch.zeros_like(image_embeds) + + return image_embeds, uncond_image_embeds + + def run_safety_checker(self, image, device, dtype): + if self.safety_checker is None: + has_nsfw_concept = None + else: + if torch.is_tensor(image): + feature_extractor_input = self.image_processor.postprocess(image, output_type="pil") + else: + feature_extractor_input = self.image_processor.numpy_to_pil(image) + safety_checker_input = self.feature_extractor(feature_extractor_input, return_tensors="pt").to(device) + image, has_nsfw_concept = self.safety_checker( + images=image, clip_input=safety_checker_input.pixel_values.to(dtype) + ) + return image, has_nsfw_concept + + def decode_latents(self, latents): + deprecation_message = "The decode_latents method is deprecated and will be removed in 1.0.0. Please use VaeImageProcessor.postprocess(...) instead" + deprecate("decode_latents", "1.0.0", deprecation_message, standard_warn=False) + + latents = 1 / self.vae.config.scaling_factor * latents + image = self.vae.decode(latents, return_dict=False)[0] + image = (image / 2 + 0.5).clamp(0, 1) + # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16 + image = image.cpu().permute(0, 2, 3, 1).float().numpy() + return image + + def prepare_extra_step_kwargs(self, generator, eta): + # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature + # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers. + # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502 + # and should be between [0, 1] + + accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys()) + extra_step_kwargs = {} + if accepts_eta: + extra_step_kwargs["eta"] = eta + + # check if the scheduler accepts generator + accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys()) + if accepts_generator: + extra_step_kwargs["generator"] = generator + return extra_step_kwargs + + def check_inputs( + self, + prompt, + height, + width, + callback_steps, + negative_prompt=None, + prompt_embeds=None, + negative_prompt_embeds=None, + callback_on_step_end_tensor_inputs=None, + ): + if height % 8 != 0 or width % 8 != 0: + raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.") + + if callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0): + raise ValueError( + f"`callback_steps` has to be a positive integer but is {callback_steps} of type" + f" {type(callback_steps)}." + ) + if callback_on_step_end_tensor_inputs is not None and not all( + k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs + ): + raise ValueError( + f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}" + ) + + if prompt is not None and prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" + " only forward one of the two." + ) + elif prompt is None and prompt_embeds is None: + raise ValueError( + "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." + ) + elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): + raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") + + if negative_prompt is not None and negative_prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:" + f" {negative_prompt_embeds}. Please make sure to only forward one of the two." + ) + + if prompt_embeds is not None and negative_prompt_embeds is not None: + if prompt_embeds.shape != negative_prompt_embeds.shape: + raise ValueError( + "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but" + f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`" + f" {negative_prompt_embeds.shape}." + ) + + def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None): + shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor) + if isinstance(generator, list) and len(generator) != batch_size: + raise ValueError( + f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" + f" size of {batch_size}. Make sure the batch size matches the length of the generators." + ) + + if latents is None: + latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) + else: + latents = latents.to(device) + + # scale the initial noise by the standard deviation required by the scheduler + latents = latents * self.scheduler.init_noise_sigma + return latents + + def enable_freeu(self, s1: float, s2: float, b1: float, b2: float): + r"""Enables the FreeU mechanism as in https://arxiv.org/abs/2309.11497. + + The suffixes after the scaling factors represent the stages where they are being applied. + + Please refer to the [official repository](https://github.com/ChenyangSi/FreeU) for combinations of the values + that are known to work well for different pipelines such as Stable Diffusion v1, v2, and Stable Diffusion XL. + + Args: + s1 (`float`): + Scaling factor for stage 1 to attenuate the contributions of the skip features. This is done to + mitigate "oversmoothing effect" in the enhanced denoising process. + s2 (`float`): + Scaling factor for stage 2 to attenuate the contributions of the skip features. This is done to + mitigate "oversmoothing effect" in the enhanced denoising process. + b1 (`float`): Scaling factor for stage 1 to amplify the contributions of backbone features. + b2 (`float`): Scaling factor for stage 2 to amplify the contributions of backbone features. + """ + if not hasattr(self, "unet"): + raise ValueError("The pipeline must have `unet` for using FreeU.") + self.unet.enable_freeu(s1=s1, s2=s2, b1=b1, b2=b2) + + def disable_freeu(self): + """Disables the FreeU mechanism if enabled.""" + self.unet.disable_freeu() + + # Copied from diffusers.pipelines.stable_diffusion_xl.pipeline_stable_diffusion_xl.StableDiffusionXLPipeline.fuse_qkv_projections + def fuse_qkv_projections(self, unet: bool = True, vae: bool = True): + """ + Enables fused QKV projections. For self-attention modules, all projection matrices (i.e., query, + key, value) are fused. For cross-attention modules, key and value projection matrices are fused. + + + + This API is 🧪 experimental. + + + + Args: + unet (`bool`, defaults to `True`): To apply fusion on the UNet. + vae (`bool`, defaults to `True`): To apply fusion on the VAE. + """ + self.fusing_unet = False + self.fusing_vae = False + + if unet: + self.fusing_unet = True + self.unet.fuse_qkv_projections() + self.unet.set_attn_processor(FusedAttnProcessor2_0()) + + if vae: + if not isinstance(self.vae, AutoencoderKL): + raise ValueError("`fuse_qkv_projections()` is only supported for the VAE of type `AutoencoderKL`.") + + self.fusing_vae = True + self.vae.fuse_qkv_projections() + self.vae.set_attn_processor(FusedAttnProcessor2_0()) + + # Copied from diffusers.pipelines.stable_diffusion_xl.pipeline_stable_diffusion_xl.StableDiffusionXLPipeline.unfuse_qkv_projections + def unfuse_qkv_projections(self, unet: bool = True, vae: bool = True): + """Disable QKV projection fusion if enabled. + + + + This API is 🧪 experimental. + + + + Args: + unet (`bool`, defaults to `True`): To apply fusion on the UNet. + vae (`bool`, defaults to `True`): To apply fusion on the VAE. + + """ + if unet: + if not self.fusing_unet: + logger.warning("The UNet was not initially fused for QKV projections. Doing nothing.") + else: + self.unet.unfuse_qkv_projections() + self.fusing_unet = False + + if vae: + if not self.fusing_vae: + logger.warning("The VAE was not initially fused for QKV projections. Doing nothing.") + else: + self.vae.unfuse_qkv_projections() + self.fusing_vae = False + + # Copied from diffusers.pipelines.latent_consistency_models.pipeline_latent_consistency_text2img.LatentConsistencyModelPipeline.get_guidance_scale_embedding + def get_guidance_scale_embedding(self, w, embedding_dim=512, dtype=torch.float32): + """ + See https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298 + + Args: + timesteps (`torch.Tensor`): + generate embedding vectors at these timesteps + embedding_dim (`int`, *optional*, defaults to 512): + dimension of the embeddings to generate + dtype: + data type of the generated embeddings + + Returns: + `torch.FloatTensor`: Embedding vectors with shape `(len(timesteps), embedding_dim)` + """ + assert len(w.shape) == 1 + w = w * 1000.0 + + half_dim = embedding_dim // 2 + emb = torch.log(torch.tensor(10000.0)) / (half_dim - 1) + emb = torch.exp(torch.arange(half_dim, dtype=dtype) * -emb) + emb = w.to(dtype)[:, None] * emb[None, :] + emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1) + if embedding_dim % 2 == 1: # zero pad + emb = torch.nn.functional.pad(emb, (0, 1)) + assert emb.shape == (w.shape[0], embedding_dim) + return emb + + @property + def guidance_scale(self): + return self._guidance_scale + + @property + def guidance_rescale(self): + return self._guidance_rescale + + @property + def clip_skip(self): + return self._clip_skip + + # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) + # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` + # corresponds to doing no classifier free guidance. + @property + def do_classifier_free_guidance(self): + return self._guidance_scale > 1 and self.unet.config.time_cond_proj_dim is None + + @property + def cross_attention_kwargs(self): + return self._cross_attention_kwargs + + @property + def num_timesteps(self): + return self._num_timesteps + + @property + def interrupt(self): + return self._interrupt + + @torch.no_grad() + def __call__( + self, + prompt: Union[str, List[str]] = None, + height: Optional[int] = None, + width: Optional[int] = None, + num_inference_steps: int = 50, + timesteps: List[int] = None, + guidance_scale: float = 7.5, + negative_prompt: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: Optional[int] = 1, + eta: float = 0.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + latents: Optional[torch.FloatTensor] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + ip_adapter_image: Optional[PipelineImageInput] = None, + ip_adapter_image_embeds: Optional[List[torch.FloatTensor]] = None, + output_type: Optional[str] = "pil", + return_dict: bool = True, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + guidance_rescale: float = 0.0, + clip_skip: Optional[int] = None, + callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None, + callback_on_step_end_tensor_inputs: List[str] = ["latents"], + **kwargs, + ): + r""" + The call function to the pipeline for generation. + + Args: + prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide image generation. If not defined, you need to pass `prompt_embeds`. + height (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`): + The height in pixels of the generated image. + width (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`): + The width in pixels of the generated image. + num_inference_steps (`int`, *optional*, defaults to 50): + The number of denoising steps. More denoising steps usually lead to a higher quality image at the + expense of slower inference. + timesteps (`List[int]`, *optional*): + Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument + in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is + passed will be used. Must be in descending order. + guidance_scale (`float`, *optional*, defaults to 7.5): + A higher guidance scale value encourages the model to generate images closely linked to the text + `prompt` at the expense of lower image quality. Guidance scale is enabled when `guidance_scale > 1`. + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide what to not include in image generation. If not defined, you need to + pass `negative_prompt_embeds` instead. Ignored when not using guidance (`guidance_scale < 1`). + num_images_per_prompt (`int`, *optional*, defaults to 1): + The number of images to generate per prompt. + eta (`float`, *optional*, defaults to 0.0): + Corresponds to parameter eta (η) from the [DDIM](https://arxiv.org/abs/2010.02502) paper. Only applies + to the [`~schedulers.DDIMScheduler`], and is ignored in other schedulers. + generator (`torch.Generator` or `List[torch.Generator]`, *optional*): + A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make + generation deterministic. + latents (`torch.FloatTensor`, *optional*): + Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for image + generation. Can be used to tweak the same generation with different prompts. If not provided, a latents + tensor is generated by sampling using the supplied random `generator`. + prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs (prompt weighting). If not + provided, text embeddings are generated from the `prompt` input argument. + negative_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs (prompt weighting). If + not provided, `negative_prompt_embeds` are generated from the `negative_prompt` input argument. + ip_adapter_image: (`PipelineImageInput`, *optional*): Optional image input to work with IP Adapters. + output_type (`str`, *optional*, defaults to `"pil"`): + The output format of the generated image. Choose between `PIL.Image` or `np.array`. + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] instead of a + plain tuple. + cross_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the [`AttentionProcessor`] as defined in + [`self.processor`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + guidance_rescale (`float`, *optional*, defaults to 0.0): + Guidance rescale factor from [Common Diffusion Noise Schedules and Sample Steps are + Flawed](https://arxiv.org/pdf/2305.08891.pdf). Guidance rescale factor should fix overexposure when + using zero terminal SNR. + clip_skip (`int`, *optional*): + Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that + the output of the pre-final layer will be used for computing the prompt embeddings. + callback_on_step_end (`Callable`, *optional*): + A function that calls at the end of each denoising steps during the inference. The function is called + with the following arguments: `callback_on_step_end(self: DiffusionPipeline, step: int, timestep: int, + callback_kwargs: Dict)`. `callback_kwargs` will include a list of all tensors as specified by + `callback_on_step_end_tensor_inputs`. + callback_on_step_end_tensor_inputs (`List`, *optional*): + The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list + will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the + `._callback_tensor_inputs` attribute of your pipeline class. + + Examples: + + Returns: + [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] or `tuple`: + If `return_dict` is `True`, [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] is returned, + otherwise a `tuple` is returned where the first element is a list with the generated images and the + second element is a list of `bool`s indicating whether the corresponding generated image contains + "not-safe-for-work" (nsfw) content. + """ + + callback = kwargs.pop("callback", None) + callback_steps = kwargs.pop("callback_steps", None) + + if callback is not None: + deprecate( + "callback", + "1.0.0", + "Passing `callback` as an input argument to `__call__` is deprecated, consider using `callback_on_step_end`", + ) + if callback_steps is not None: + deprecate( + "callback_steps", + "1.0.0", + "Passing `callback_steps` as an input argument to `__call__` is deprecated, consider using `callback_on_step_end`", + ) + + # 0. Default height and width to unet + height = height or self.unet.config.sample_size * self.vae_scale_factor + width = width or self.unet.config.sample_size * self.vae_scale_factor + # to deal with lora scaling and other possible forward hooks + + # 1. Check inputs. Raise error if not correct + self.check_inputs( + prompt, + height, + width, + callback_steps, + negative_prompt, + prompt_embeds, + negative_prompt_embeds, + callback_on_step_end_tensor_inputs, + ) + + self._guidance_scale = guidance_scale + self._guidance_rescale = guidance_rescale + self._clip_skip = clip_skip + self._cross_attention_kwargs = cross_attention_kwargs + self._interrupt = False + + # 2. Define call parameters + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + device = self._execution_device + + # 3. Encode input prompt + lora_scale = ( + self.cross_attention_kwargs.get("scale", None) if self.cross_attention_kwargs is not None else None + ) + + prompt_embeds, negative_prompt_embeds = self.encode_prompt( + prompt, + device, + num_images_per_prompt, + self.do_classifier_free_guidance, + negative_prompt, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + lora_scale=lora_scale, + clip_skip=self.clip_skip, + ) + + # For classifier free guidance, we need to do two forward passes. + # Here we concatenate the unconditional and text embeddings into a single batch + # to avoid doing two forward passes + if self.do_classifier_free_guidance: + if prompt_embeds.shape != negative_prompt_embeds.shape: + tmp_embeds = negative_prompt_embeds.clone() + tmp_embeds[:,0:1,:] = prompt_embeds + prompt_embeds = tmp_embeds + prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds]) + # TODO + if ip_adapter_image is not None or ip_adapter_image_embeds is not None: + image_embeds = self.prepare_ip_adapter_image_embeds( + ip_adapter_image, + ip_adapter_image_embeds, + device, + batch_size * num_images_per_prompt, + self.do_classifier_free_guidance, + ) + # if ip_adapter_image is not None: + # if self.unet.multi_frames_condition: + # output_hidden_state = False if isinstance(self.unet.encoder_hid_proj, VideoProjModel) else True + # else: + # output_hidden_state = False if isinstance(self.unet.encoder_hid_proj, ImageProjection) else True + # # NOTE: ip_adapter_image shold be list with len() == 50 + # image_embeds, negative_image_embeds = self.encode_image( + # ip_adapter_image, device, num_images_per_prompt, output_hidden_state + # ) + # # import ipdb; ipdb.set_trace() + # image_embeds = image_embeds.unsqueeze(0) + # negative_image_embeds = negative_image_embeds.unsqueeze(0) + # if not self.unet.multi_frames_condition: + # image_embeds = torch.mean(image_embeds, dim=1, keepdim=False) + # negative_image_embeds = negative_image_embeds[:,0, ...] + + # if self.do_classifier_free_guidance: + # image_embeds = torch.cat([negative_image_embeds, image_embeds]) + + # 4. Prepare timesteps + timesteps, num_inference_steps = retrieve_timesteps(self.scheduler, num_inference_steps, device, timesteps) + + # 5. Prepare latent variables + num_channels_latents = self.unet.config.in_channels + latents = self.prepare_latents( + batch_size * num_images_per_prompt, + num_channels_latents, + height, + width, + prompt_embeds.dtype, + device, + generator, + latents, + ) + + # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline + extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) + + # 6.1 Add image embeds for IP-Adapter + added_cond_kwargs = {"image_embeds": image_embeds} if ip_adapter_image is not None else None + + # 6.2 Optionally get Guidance Scale Embedding + timestep_cond = None + if self.unet.config.time_cond_proj_dim is not None: + guidance_scale_tensor = torch.tensor(self.guidance_scale - 1).repeat(batch_size * num_images_per_prompt) + timestep_cond = self.get_guidance_scale_embedding( + guidance_scale_tensor, embedding_dim=self.unet.config.time_cond_proj_dim + ).to(device=device, dtype=latents.dtype) + + # 7. Denoising loop + num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order + self._num_timesteps = len(timesteps) + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + if self.interrupt: + continue + + # expand the latents if we are doing classifier free guidance + latent_model_input = torch.cat([latents] * 2) if self.do_classifier_free_guidance else latents + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + # predict the noise residual + noise_pred = self.unet( + latent_model_input, + t, + encoder_hidden_states=prompt_embeds, + timestep_cond=timestep_cond, + cross_attention_kwargs=self.cross_attention_kwargs, + added_cond_kwargs=added_cond_kwargs, + return_dict=False, + )[0] + + # perform guidance + if self.do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) + noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond) + + if self.do_classifier_free_guidance and self.guidance_rescale > 0.0: + # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf + noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=self.guidance_rescale) + + # compute the previous noisy sample x_t -> x_t-1 + latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] + + if callback_on_step_end is not None: + callback_kwargs = {} + for k in callback_on_step_end_tensor_inputs: + callback_kwargs[k] = locals()[k] + callback_outputs = callback_on_step_end(self, i, t, callback_kwargs) + + latents = callback_outputs.pop("latents", latents) + prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds) + negative_prompt_embeds = callback_outputs.pop("negative_prompt_embeds", negative_prompt_embeds) + + # call the callback, if provided + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + if callback is not None and i % callback_steps == 0: + step_idx = i // getattr(self.scheduler, "order", 1) + callback(step_idx, t, latents) + + if not output_type == "latent": + image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False, generator=generator)[ + 0 + ] + image, has_nsfw_concept = self.run_safety_checker(image, device, prompt_embeds.dtype) + else: + image = latents + has_nsfw_concept = None + + if has_nsfw_concept is None: + do_denormalize = [True] * image.shape[0] + else: + do_denormalize = [not has_nsfw for has_nsfw in has_nsfw_concept] + + image = self.image_processor.postprocess(image, output_type=output_type, do_denormalize=do_denormalize) + + # Offload all models + self.maybe_free_model_hooks() + + if not return_dict: + return (image, has_nsfw_concept) + + return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept) \ No newline at end of file diff --git a/foleycrafter/pipelines/pipeline_controlnet.py b/foleycrafter/pipelines/pipeline_controlnet.py new file mode 100644 index 0000000000000000000000000000000000000000..11f1de506080f840224d9111be642082e5ad5f5c --- /dev/null +++ b/foleycrafter/pipelines/pipeline_controlnet.py @@ -0,0 +1,1340 @@ +# Copyright 2023 The HuggingFace 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 +# limitations under the License. + + +import inspect +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +import numpy as np +import PIL.Image +import torch +import torch.nn.functional as F +from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer, CLIPVisionModelWithProjection + +from diffusers.image_processor import PipelineImageInput, VaeImageProcessor +from diffusers.loaders import FromSingleFileMixin, LoraLoaderMixin, TextualInversionLoaderMixin +from diffusers.models import AutoencoderKL, ControlNetModel, ImageProjection +from diffusers.models.lora import adjust_lora_scale_text_encoder +from diffusers.schedulers import KarrasDiffusionSchedulers +from diffusers.utils import ( + USE_PEFT_BACKEND, + deprecate, + logging, + replace_example_docstring, + scale_lora_layers, + unscale_lora_layers, +) +from diffusers.utils.torch_utils import is_compiled_module, is_torch_version, randn_tensor +from diffusers.pipelines.pipeline_utils import DiffusionPipeline +from diffusers.pipelines.stable_diffusion.pipeline_output import StableDiffusionPipelineOutput +from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker +from diffusers.pipelines.controlnet.multicontrolnet import MultiControlNetModel + +from foleycrafter.models.auffusion_unet import UNet2DConditionModel +from foleycrafter.models.auffusion.loaders.ip_adapter import IPAdapterMixin + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + +EXAMPLE_DOC_STRING = """ + Examples: + ```py + >>> # !pip install opencv-python transformers accelerate + >>> from diffusers import StableDiffusionControlNetPipeline, ControlNetModel, UniPCMultistepScheduler + >>> from diffusers.utils import load_image + >>> import numpy as np + >>> import torch + + >>> import cv2 + >>> from PIL import Image + + >>> # download an image + >>> image = load_image( + ... "https://hf.co/datasets/huggingface/documentation-images/resolve/main/diffusers/input_image_vermeer.png" + ... ) + >>> image = np.array(image) + + >>> # get canny image + >>> image = cv2.Canny(image, 100, 200) + >>> image = image[:, :, None] + >>> image = np.concatenate([image, image, image], axis=2) + >>> canny_image = Image.fromarray(image) + + >>> # load control net and stable diffusion v1-5 + >>> controlnet = ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-canny", torch_dtype=torch.float16) + >>> pipe = StableDiffusionControlNetPipeline.from_pretrained( + ... "runwayml/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16 + ... ) + + >>> # speed up diffusion process with faster scheduler and memory optimization + >>> pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config) + >>> # remove following line if xformers is not installed + >>> pipe.enable_xformers_memory_efficient_attention() + + >>> pipe.enable_model_cpu_offload() + + >>> # generate image + >>> generator = torch.manual_seed(0) + >>> image = pipe( + ... "futuristic-looking woman", num_inference_steps=20, generator=generator, image=canny_image + ... ).images[0] + ``` +""" + + +# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps +def retrieve_timesteps( + scheduler, + num_inference_steps: Optional[int] = None, + device: Optional[Union[str, torch.device]] = None, + timesteps: Optional[List[int]] = None, + **kwargs, +): + """ + Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles + custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. + + Args: + scheduler (`SchedulerMixin`): + The scheduler to get timesteps from. + num_inference_steps (`int`): + The number of diffusion steps used when generating samples with a pre-trained model. If used, + `timesteps` must be `None`. + device (`str` or `torch.device`, *optional*): + The device to which the timesteps should be moved to. If `None`, the timesteps are not moved. + timesteps (`List[int]`, *optional*): + Custom timesteps used to support arbitrary spacing between timesteps. If `None`, then the default + timestep spacing strategy of the scheduler is used. If `timesteps` is passed, `num_inference_steps` + must be `None`. + + Returns: + `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the + second element is the number of inference steps. + """ + if timesteps is not None: + accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) + if not accepts_timesteps: + raise ValueError( + f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" + f" timestep schedules. Please check whether you are using the correct scheduler." + ) + scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) + timesteps = scheduler.timesteps + num_inference_steps = len(timesteps) + else: + scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) + timesteps = scheduler.timesteps + return timesteps, num_inference_steps + +class StableDiffusionControlNetPipeline( + DiffusionPipeline, TextualInversionLoaderMixin, LoraLoaderMixin, IPAdapterMixin, FromSingleFileMixin +): + r""" + Pipeline for text-to-image generation using Stable Diffusion with ControlNet guidance. + + This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods + implemented for all pipelines (downloading, saving, running on a particular device, etc.). + + The pipeline also inherits the following loading methods: + - [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] for loading textual inversion embeddings + - [`~loaders.LoraLoaderMixin.load_lora_weights`] for loading LoRA weights + - [`~loaders.LoraLoaderMixin.save_lora_weights`] for saving LoRA weights + - [`~loaders.FromSingleFileMixin.from_single_file`] for loading `.ckpt` files + - [`~loaders.IPAdapterMixin.load_ip_adapter`] for loading IP Adapters + + Args: + vae ([`AutoencoderKL`]): + Variational Auto-Encoder (VAE) model to encode and decode images to and from latent representations. + text_encoder ([`~transformers.CLIPTextModel`]): + Frozen text-encoder ([clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14)). + tokenizer ([`~transformers.CLIPTokenizer`]): + A `CLIPTokenizer` to tokenize text. + unet ([`UNet2DConditionModel`]): + A `UNet2DConditionModel` to denoise the encoded image latents. + controlnet ([`ControlNetModel`] or `List[ControlNetModel]`): + Provides additional conditioning to the `unet` during the denoising process. If you set multiple + ControlNets as a list, the outputs from each ControlNet are added together to create one combined + additional conditioning. + scheduler ([`SchedulerMixin`]): + A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of + [`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`]. + safety_checker ([`StableDiffusionSafetyChecker`]): + Classification module that estimates whether generated images could be considered offensive or harmful. + Please refer to the [model card](https://huggingface.co/runwayml/stable-diffusion-v1-5) for more details + about a model's potential harms. + feature_extractor ([`~transformers.CLIPImageProcessor`]): + A `CLIPImageProcessor` to extract features from generated images; used as inputs to the `safety_checker`. + """ + + model_cpu_offload_seq = "text_encoder->image_encoder->unet->vae" + _optional_components = ["safety_checker", "feature_extractor", "image_encoder"] + _exclude_from_cpu_offload = ["safety_checker"] + _callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds"] + + def __init__( + self, + vae: AutoencoderKL, + text_encoder: CLIPTextModel, + tokenizer: CLIPTokenizer, + unet: UNet2DConditionModel, + controlnet: Union[ControlNetModel, List[ControlNetModel], Tuple[ControlNetModel], MultiControlNetModel], + scheduler: KarrasDiffusionSchedulers, + safety_checker: StableDiffusionSafetyChecker, + feature_extractor: CLIPImageProcessor, + image_encoder: CLIPVisionModelWithProjection = None, + requires_safety_checker: bool = True, + ): + super().__init__() + + if safety_checker is None and requires_safety_checker: + logger.warning( + f"You have disabled the safety checker for {self.__class__} by passing `safety_checker=None`. Ensure" + " that you abide to the conditions of the Stable Diffusion license and do not expose unfiltered" + " results in services or applications open to the public. Both the diffusers team and Hugging Face" + " strongly recommend to keep the safety filter enabled in all public facing circumstances, disabling" + " it only for use-cases that involve analyzing network behavior or auditing its results. For more" + " information, please have a look at https://github.com/huggingface/diffusers/pull/254 ." + ) + + if safety_checker is not None and feature_extractor is None: + raise ValueError( + "Make sure to define a feature extractor when loading {self.__class__} if you want to use the safety" + " checker. If you do not want to use the safety checker, you can pass `'safety_checker=None'` instead." + ) + + if isinstance(controlnet, (list, tuple)): + controlnet = MultiControlNetModel(controlnet) + + self.register_modules( + vae=vae, + text_encoder=text_encoder, + tokenizer=tokenizer, + unet=unet, + controlnet=controlnet, + scheduler=scheduler, + safety_checker=safety_checker, + feature_extractor=feature_extractor, + image_encoder=image_encoder, + ) + self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) + self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor, do_convert_rgb=True) + self.control_image_processor = VaeImageProcessor( + vae_scale_factor=self.vae_scale_factor, do_convert_rgb=True, do_normalize=False + ) + self.register_to_config(requires_safety_checker=requires_safety_checker) + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_vae_slicing + def enable_vae_slicing(self): + r""" + Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to + compute decoding in several steps. This is useful to save some memory and allow larger batch sizes. + """ + self.vae.enable_slicing() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_vae_slicing + def disable_vae_slicing(self): + r""" + Disable sliced VAE decoding. If `enable_vae_slicing` was previously enabled, this method will go back to + computing decoding in one step. + """ + self.vae.disable_slicing() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_vae_tiling + def enable_vae_tiling(self): + r""" + Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to + compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow + processing larger images. + """ + self.vae.enable_tiling() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_vae_tiling + def disable_vae_tiling(self): + r""" + Disable tiled VAE decoding. If `enable_vae_tiling` was previously enabled, this method will go back to + computing decoding in one step. + """ + self.vae.disable_tiling() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt + def _encode_prompt( + self, + prompt, + device, + num_images_per_prompt, + do_classifier_free_guidance, + negative_prompt=None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + lora_scale: Optional[float] = None, + **kwargs, + ): + deprecation_message = "`_encode_prompt()` is deprecated and it will be removed in a future version. Use `encode_prompt()` instead. Also, be aware that the output format changed from a concatenated tensor to a tuple." + deprecate("_encode_prompt()", "1.0.0", deprecation_message, standard_warn=False) + + prompt_embeds_tuple = self.encode_prompt( + prompt=prompt, + device=device, + num_images_per_prompt=num_images_per_prompt, + do_classifier_free_guidance=do_classifier_free_guidance, + negative_prompt=negative_prompt, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + lora_scale=lora_scale, + **kwargs, + ) + + # concatenate for backwards comp + prompt_embeds = torch.cat([prompt_embeds_tuple[1], prompt_embeds_tuple[0]]) + + return prompt_embeds + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.encode_prompt + def encode_prompt( + self, + prompt, + device, + num_images_per_prompt, + do_classifier_free_guidance, + negative_prompt=None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + lora_scale: Optional[float] = None, + clip_skip: Optional[int] = None, + ): + r""" + Encodes the prompt into text encoder hidden states. + + Args: + prompt (`str` or `List[str]`, *optional*): + prompt to be encoded + device: (`torch.device`): + torch device + num_images_per_prompt (`int`): + number of images that should be generated per prompt + do_classifier_free_guidance (`bool`): + whether to use classifier free guidance or not + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation. If not defined, one has to pass + `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is + less than `1`). + prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not + provided, text embeddings will be generated from `prompt` input argument. + negative_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input + argument. + lora_scale (`float`, *optional*): + A LoRA scale that will be applied to all LoRA layers of the text encoder if LoRA layers are loaded. + clip_skip (`int`, *optional*): + Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that + the output of the pre-final layer will be used for computing the prompt embeddings. + """ + # set lora scale so that monkey patched LoRA + # function of text encoder can correctly access it + if lora_scale is not None and isinstance(self, LoraLoaderMixin): + self._lora_scale = lora_scale + + # dynamically adjust the LoRA scale + if not USE_PEFT_BACKEND: + adjust_lora_scale_text_encoder(self.text_encoder, lora_scale) + else: + scale_lora_layers(self.text_encoder, lora_scale) + + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + if prompt_embeds is None: + # textual inversion: procecss multi-vector tokens if necessary + if isinstance(self, TextualInversionLoaderMixin): + prompt = self.maybe_convert_prompt(prompt, self.tokenizer) + + text_inputs = self.tokenizer( + prompt, + padding="max_length", + max_length=self.tokenizer.model_max_length, + truncation=True, + return_tensors="pt", + ) + text_input_ids = text_inputs.input_ids + untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids + + if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal( + text_input_ids, untruncated_ids + ): + removed_text = self.tokenizer.batch_decode( + untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1] + ) + logger.warning( + "The following part of your input was truncated because CLIP can only handle sequences up to" + f" {self.tokenizer.model_max_length} tokens: {removed_text}" + ) + + if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask: + attention_mask = text_inputs.attention_mask.to(device) + else: + attention_mask = None + + if clip_skip is None: + prompt_embeds = self.text_encoder(text_input_ids.to(device), attention_mask=attention_mask) + prompt_embeds = prompt_embeds[0] + else: + prompt_embeds = self.text_encoder( + text_input_ids.to(device), attention_mask=attention_mask, output_hidden_states=True + ) + # Access the `hidden_states` first, that contains a tuple of + # all the hidden states from the encoder layers. Then index into + # the tuple to access the hidden states from the desired layer. + prompt_embeds = prompt_embeds[-1][-(clip_skip + 1)] + # We also need to apply the final LayerNorm here to not mess with the + # representations. The `last_hidden_states` that we typically use for + # obtaining the final prompt representations passes through the LayerNorm + # layer. + prompt_embeds = self.text_encoder.text_model.final_layer_norm(prompt_embeds) + + if self.text_encoder is not None: + prompt_embeds_dtype = self.text_encoder.dtype + elif self.unet is not None: + prompt_embeds_dtype = self.unet.dtype + else: + prompt_embeds_dtype = prompt_embeds.dtype + + prompt_embeds = prompt_embeds.to(dtype=prompt_embeds_dtype, device=device) + + bs_embed, seq_len, _ = prompt_embeds.shape + # duplicate text embeddings for each generation per prompt, using mps friendly method + prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) + prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1) + + # get unconditional embeddings for classifier free guidance + if do_classifier_free_guidance and negative_prompt_embeds is None: + uncond_tokens: List[str] + if negative_prompt is None: + uncond_tokens = [""] * batch_size + elif prompt is not None and type(prompt) is not type(negative_prompt): + raise TypeError( + f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" + f" {type(prompt)}." + ) + elif isinstance(negative_prompt, str): + uncond_tokens = [negative_prompt] + elif batch_size != len(negative_prompt): + raise ValueError( + f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" + f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" + " the batch size of `prompt`." + ) + else: + uncond_tokens = negative_prompt + + # textual inversion: procecss multi-vector tokens if necessary + if isinstance(self, TextualInversionLoaderMixin): + uncond_tokens = self.maybe_convert_prompt(uncond_tokens, self.tokenizer) + + max_length = prompt_embeds.shape[1] + uncond_input = self.tokenizer( + uncond_tokens, + padding="max_length", + max_length=max_length, + truncation=True, + return_tensors="pt", + ) + + if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask: + attention_mask = uncond_input.attention_mask.to(device) + else: + attention_mask = None + + negative_prompt_embeds = self.text_encoder( + uncond_input.input_ids.to(device), + attention_mask=attention_mask, + ) + negative_prompt_embeds = negative_prompt_embeds[0] + + if do_classifier_free_guidance: + # duplicate unconditional embeddings for each generation per prompt, using mps friendly method + seq_len = negative_prompt_embeds.shape[1] + + negative_prompt_embeds = negative_prompt_embeds.to(dtype=prompt_embeds_dtype, device=device) + + negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1) + negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) + + if isinstance(self, LoraLoaderMixin) and USE_PEFT_BACKEND: + # Retrieve the original scale by scaling back the LoRA layers + unscale_lora_layers(self.text_encoder, lora_scale) + + return prompt_embeds, negative_prompt_embeds + + def prepare_ip_adapter_image_embeds( + self, ip_adapter_image, ip_adapter_image_embeds, device, num_images_per_prompt, do_classifier_free_guidance + ): + if ip_adapter_image_embeds is None: + if not isinstance(ip_adapter_image, list): + ip_adapter_image = [ip_adapter_image] + + if len(ip_adapter_image) != len(self.unet.encoder_hid_proj.image_projection_layers): + raise ValueError( + f"`ip_adapter_image` must have same length as the number of IP Adapters. Got {len(ip_adapter_image)} images and {len(self.unet.encoder_hid_proj.image_projection_layers)} IP Adapters." + ) + + image_embeds = [] + for single_ip_adapter_image, image_proj_layer in zip( + ip_adapter_image, self.unet.encoder_hid_proj.image_projection_layers + ): + output_hidden_state = not isinstance(image_proj_layer, ImageProjection) + single_image_embeds, single_negative_image_embeds = self.encode_image( + single_ip_adapter_image, device, 1, output_hidden_state + ) + single_image_embeds = torch.stack([single_image_embeds] * num_images_per_prompt, dim=0) + single_negative_image_embeds = torch.stack( + [single_negative_image_embeds] * num_images_per_prompt, dim=0 + ) + + if do_classifier_free_guidance: + single_image_embeds = torch.cat([single_negative_image_embeds, single_image_embeds]) + single_image_embeds = single_image_embeds.to(device) + + image_embeds.append(single_image_embeds) + else: + repeat_dims = [1] + image_embeds = [] + for single_image_embeds in ip_adapter_image_embeds: + if do_classifier_free_guidance: + single_negative_image_embeds, single_image_embeds = single_image_embeds.chunk(2) + single_image_embeds = single_image_embeds.repeat( + num_images_per_prompt, *(repeat_dims * len(single_image_embeds.shape[1:])) + ) + single_negative_image_embeds = single_negative_image_embeds.repeat( + num_images_per_prompt, *(repeat_dims * len(single_negative_image_embeds.shape[1:])) + ) + single_image_embeds = torch.cat([single_negative_image_embeds, single_image_embeds]) + else: + single_image_embeds = single_image_embeds.repeat( + num_images_per_prompt, *(repeat_dims * len(single_image_embeds.shape[1:])) + ) + image_embeds.append(single_image_embeds) + + return image_embeds + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.encode_image + def encode_image(self, image, device, num_images_per_prompt, output_hidden_states=None): + dtype = next(self.image_encoder.parameters()).dtype + + if not isinstance(image, torch.Tensor): + image = self.feature_extractor(image, return_tensors="pt").pixel_values + + image = image.to(device=device, dtype=dtype) + if output_hidden_states: + image_enc_hidden_states = self.image_encoder(image, output_hidden_states=True).hidden_states[-2] + image_enc_hidden_states = image_enc_hidden_states.repeat_interleave(num_images_per_prompt, dim=0) + uncond_image_enc_hidden_states = self.image_encoder( + torch.zeros_like(image), output_hidden_states=True + ).hidden_states[-2] + uncond_image_enc_hidden_states = uncond_image_enc_hidden_states.repeat_interleave( + num_images_per_prompt, dim=0 + ) + return image_enc_hidden_states, uncond_image_enc_hidden_states + else: + image_embeds = self.image_encoder(image).image_embeds + image_embeds = image_embeds.repeat_interleave(num_images_per_prompt, dim=0) + uncond_image_embeds = torch.zeros_like(image_embeds) + + return image_embeds, uncond_image_embeds + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.run_safety_checker + def run_safety_checker(self, image, device, dtype): + if self.safety_checker is None: + has_nsfw_concept = None + else: + if torch.is_tensor(image): + feature_extractor_input = self.image_processor.postprocess(image, output_type="pil") + else: + feature_extractor_input = self.image_processor.numpy_to_pil(image) + safety_checker_input = self.feature_extractor(feature_extractor_input, return_tensors="pt").to(device) + image, has_nsfw_concept = self.safety_checker( + images=image, clip_input=safety_checker_input.pixel_values.to(dtype) + ) + return image, has_nsfw_concept + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.decode_latents + def decode_latents(self, latents): + deprecation_message = "The decode_latents method is deprecated and will be removed in 1.0.0. Please use VaeImageProcessor.postprocess(...) instead" + deprecate("decode_latents", "1.0.0", deprecation_message, standard_warn=False) + + latents = 1 / self.vae.config.scaling_factor * latents + image = self.vae.decode(latents, return_dict=False)[0] + image = (image / 2 + 0.5).clamp(0, 1) + # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16 + image = image.cpu().permute(0, 2, 3, 1).float().numpy() + return image + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs + def prepare_extra_step_kwargs(self, generator, eta): + # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature + # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers. + # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502 + # and should be between [0, 1] + + accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys()) + extra_step_kwargs = {} + if accepts_eta: + extra_step_kwargs["eta"] = eta + + # check if the scheduler accepts generator + accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys()) + if accepts_generator: + extra_step_kwargs["generator"] = generator + return extra_step_kwargs + + def check_inputs( + self, + prompt, + image, + callback_steps, + negative_prompt=None, + prompt_embeds=None, + negative_prompt_embeds=None, + controlnet_conditioning_scale=1.0, + control_guidance_start=0.0, + control_guidance_end=1.0, + callback_on_step_end_tensor_inputs=None, + ): + if callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0): + raise ValueError( + f"`callback_steps` has to be a positive integer but is {callback_steps} of type" + f" {type(callback_steps)}." + ) + + if callback_on_step_end_tensor_inputs is not None and not all( + k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs + ): + raise ValueError( + f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}" + ) + + if prompt is not None and prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" + " only forward one of the two." + ) + elif prompt is None and prompt_embeds is None: + raise ValueError( + "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." + ) + elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): + raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") + + if negative_prompt is not None and negative_prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:" + f" {negative_prompt_embeds}. Please make sure to only forward one of the two." + ) + + if prompt_embeds is not None and negative_prompt_embeds is not None: + if prompt_embeds.shape != negative_prompt_embeds.shape: + raise ValueError( + "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but" + f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`" + f" {negative_prompt_embeds.shape}." + ) + + # `prompt` needs more sophisticated handling when there are multiple + # conditionings. + if isinstance(self.controlnet, MultiControlNetModel): + if isinstance(prompt, list): + logger.warning( + f"You have {len(self.controlnet.nets)} ControlNets and you have passed {len(prompt)}" + " prompts. The conditionings will be fixed across the prompts." + ) + + # Check `image` + is_compiled = hasattr(F, "scaled_dot_product_attention") and isinstance( + self.controlnet, torch._dynamo.eval_frame.OptimizedModule + ) + if ( + isinstance(self.controlnet, ControlNetModel) + or is_compiled + and isinstance(self.controlnet._orig_mod, ControlNetModel) + ): + self.check_image(image, prompt, prompt_embeds) + elif ( + isinstance(self.controlnet, MultiControlNetModel) + or is_compiled + and isinstance(self.controlnet._orig_mod, MultiControlNetModel) + ): + if not isinstance(image, list): + raise TypeError("For multiple controlnets: `image` must be type `list`") + + # When `image` is a nested list: + # (e.g. [[canny_image_1, pose_image_1], [canny_image_2, pose_image_2]]) + elif any(isinstance(i, list) for i in image): + raise ValueError("A single batch of multiple conditionings is not supported at the moment.") + elif len(image) != len(self.controlnet.nets): + raise ValueError( + f"For multiple controlnets: `image` must have the same length as the number of controlnets, but got {len(image)} images and {len(self.controlnet.nets)} ControlNets." + ) + + for image_ in image: + self.check_image(image_, prompt, prompt_embeds) + else: + assert False + + # Check `controlnet_conditioning_scale` + if ( + isinstance(self.controlnet, ControlNetModel) + or is_compiled + and isinstance(self.controlnet._orig_mod, ControlNetModel) + ): + if not isinstance(controlnet_conditioning_scale, float): + raise TypeError("For single controlnet: `controlnet_conditioning_scale` must be type `float`.") + elif ( + isinstance(self.controlnet, MultiControlNetModel) + or is_compiled + and isinstance(self.controlnet._orig_mod, MultiControlNetModel) + ): + if isinstance(controlnet_conditioning_scale, list): + if any(isinstance(i, list) for i in controlnet_conditioning_scale): + raise ValueError("A single batch of multiple conditionings is not supported at the moment.") + elif isinstance(controlnet_conditioning_scale, list) and len(controlnet_conditioning_scale) != len( + self.controlnet.nets + ): + raise ValueError( + "For multiple controlnets: When `controlnet_conditioning_scale` is specified as `list`, it must have" + " the same length as the number of controlnets" + ) + else: + assert False + + if not isinstance(control_guidance_start, (tuple, list)): + control_guidance_start = [control_guidance_start] + + if not isinstance(control_guidance_end, (tuple, list)): + control_guidance_end = [control_guidance_end] + + if len(control_guidance_start) != len(control_guidance_end): + raise ValueError( + f"`control_guidance_start` has {len(control_guidance_start)} elements, but `control_guidance_end` has {len(control_guidance_end)} elements. Make sure to provide the same number of elements to each list." + ) + + if isinstance(self.controlnet, MultiControlNetModel): + if len(control_guidance_start) != len(self.controlnet.nets): + raise ValueError( + f"`control_guidance_start`: {control_guidance_start} has {len(control_guidance_start)} elements but there are {len(self.controlnet.nets)} controlnets available. Make sure to provide {len(self.controlnet.nets)}." + ) + + for start, end in zip(control_guidance_start, control_guidance_end): + if start >= end: + raise ValueError( + f"control guidance start: {start} cannot be larger or equal to control guidance end: {end}." + ) + if start < 0.0: + raise ValueError(f"control guidance start: {start} can't be smaller than 0.") + if end > 1.0: + raise ValueError(f"control guidance end: {end} can't be larger than 1.0.") + + def check_image(self, image, prompt, prompt_embeds): + image_is_pil = isinstance(image, PIL.Image.Image) + image_is_tensor = isinstance(image, torch.Tensor) + image_is_np = isinstance(image, np.ndarray) + image_is_pil_list = isinstance(image, list) and isinstance(image[0], PIL.Image.Image) + image_is_tensor_list = isinstance(image, list) and isinstance(image[0], torch.Tensor) + image_is_np_list = isinstance(image, list) and isinstance(image[0], np.ndarray) + + if ( + not image_is_pil + and not image_is_tensor + and not image_is_np + and not image_is_pil_list + and not image_is_tensor_list + and not image_is_np_list + ): + raise TypeError( + f"image must be passed and be one of PIL image, numpy array, torch tensor, list of PIL images, list of numpy arrays or list of torch tensors, but is {type(image)}" + ) + + if image_is_pil: + image_batch_size = 1 + else: + image_batch_size = len(image) + + if prompt is not None and isinstance(prompt, str): + prompt_batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + prompt_batch_size = len(prompt) + elif prompt_embeds is not None: + prompt_batch_size = prompt_embeds.shape[0] + + if image_batch_size != 1 and image_batch_size != prompt_batch_size: + raise ValueError( + f"If image batch size is not 1, image batch size must be same as prompt batch size. image batch size: {image_batch_size}, prompt batch size: {prompt_batch_size}" + ) + + def prepare_image( + self, + image, + width, + height, + batch_size, + num_images_per_prompt, + device, + dtype, + do_classifier_free_guidance=False, + guess_mode=False, + ): + image = self.control_image_processor.preprocess(image, height=height, width=width).to(dtype=torch.float32) + image_batch_size = image.shape[0] + + if image_batch_size == 1: + repeat_by = batch_size + else: + # image batch size is the same as prompt batch size + repeat_by = num_images_per_prompt + + image = image.repeat_interleave(repeat_by, dim=0) + + image = image.to(device=device, dtype=dtype) + + if do_classifier_free_guidance and not guess_mode: + image = torch.cat([image] * 2) + + return image + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_latents + def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None): + shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor) + if isinstance(generator, list) and len(generator) != batch_size: + raise ValueError( + f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" + f" size of {batch_size}. Make sure the batch size matches the length of the generators." + ) + + if latents is None: + latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) + else: + latents = latents.to(device) + + # scale the initial noise by the standard deviation required by the scheduler + latents = latents * self.scheduler.init_noise_sigma + return latents + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_freeu + def enable_freeu(self, s1: float, s2: float, b1: float, b2: float): + r"""Enables the FreeU mechanism as in https://arxiv.org/abs/2309.11497. + + The suffixes after the scaling factors represent the stages where they are being applied. + + Please refer to the [official repository](https://github.com/ChenyangSi/FreeU) for combinations of the values + that are known to work well for different pipelines such as Stable Diffusion v1, v2, and Stable Diffusion XL. + + Args: + s1 (`float`): + Scaling factor for stage 1 to attenuate the contributions of the skip features. This is done to + mitigate "oversmoothing effect" in the enhanced denoising process. + s2 (`float`): + Scaling factor for stage 2 to attenuate the contributions of the skip features. This is done to + mitigate "oversmoothing effect" in the enhanced denoising process. + b1 (`float`): Scaling factor for stage 1 to amplify the contributions of backbone features. + b2 (`float`): Scaling factor for stage 2 to amplify the contributions of backbone features. + """ + if not hasattr(self, "unet"): + raise ValueError("The pipeline must have `unet` for using FreeU.") + self.unet.enable_freeu(s1=s1, s2=s2, b1=b1, b2=b2) + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_freeu + def disable_freeu(self): + """Disables the FreeU mechanism if enabled.""" + self.unet.disable_freeu() + + # Copied from diffusers.pipelines.latent_consistency_models.pipeline_latent_consistency_text2img.LatentConsistencyModelPipeline.get_guidance_scale_embedding + def get_guidance_scale_embedding(self, w, embedding_dim=512, dtype=torch.float32): + """ + See https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298 + + Args: + timesteps (`torch.Tensor`): + generate embedding vectors at these timesteps + embedding_dim (`int`, *optional*, defaults to 512): + dimension of the embeddings to generate + dtype: + data type of the generated embeddings + + Returns: + `torch.FloatTensor`: Embedding vectors with shape `(len(timesteps), embedding_dim)` + """ + assert len(w.shape) == 1 + w = w * 1000.0 + + half_dim = embedding_dim // 2 + emb = torch.log(torch.tensor(10000.0)) / (half_dim - 1) + emb = torch.exp(torch.arange(half_dim, dtype=dtype) * -emb) + emb = w.to(dtype)[:, None] * emb[None, :] + emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1) + if embedding_dim % 2 == 1: # zero pad + emb = torch.nn.functional.pad(emb, (0, 1)) + assert emb.shape == (w.shape[0], embedding_dim) + return emb + + @property + def guidance_scale(self): + return self._guidance_scale + + @property + def clip_skip(self): + return self._clip_skip + + # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) + # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` + # corresponds to doing no classifier free guidance. + @property + def do_classifier_free_guidance(self): + return self._guidance_scale > 1 and self.unet.config.time_cond_proj_dim is None + + @property + def cross_attention_kwargs(self): + return self._cross_attention_kwargs + + @property + def num_timesteps(self): + return self._num_timesteps + + @torch.no_grad() + @replace_example_docstring(EXAMPLE_DOC_STRING) + def __call__( + self, + prompt: Union[str, List[str]] = None, + image: PipelineImageInput = None, + height: Optional[int] = None, + width: Optional[int] = None, + num_inference_steps: int = 50, + timesteps: List[int] = None, + guidance_scale: float = 7.5, + negative_prompt: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: Optional[int] = 1, + eta: float = 0.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + latents: Optional[torch.FloatTensor] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + ip_adapter_image: Optional[PipelineImageInput] = None, + ip_adapter_image_embeds: Optional[List[torch.FloatTensor]] = None, + output_type: Optional[str] = "pil", + return_dict: bool = True, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + controlnet_conditioning_scale: Union[float, List[float]] = 1.0, + controlnet_prompt_embeds: Optional[List[torch.FloatTensor]] = None, + guess_mode: bool = False, + control_guidance_start: Union[float, List[float]] = 0.0, + control_guidance_end: Union[float, List[float]] = 1.0, + clip_skip: Optional[int] = None, + callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None, + callback_on_step_end_tensor_inputs: List[str] = ["latents"], + **kwargs, + ): + r""" + The call function to the pipeline for generation. + + Args: + prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide image generation. If not defined, you need to pass `prompt_embeds`. + image (`torch.FloatTensor`, `PIL.Image.Image`, `np.ndarray`, `List[torch.FloatTensor]`, `List[PIL.Image.Image]`, `List[np.ndarray]`,: + `List[List[torch.FloatTensor]]`, `List[List[np.ndarray]]` or `List[List[PIL.Image.Image]]`): + The ControlNet input condition to provide guidance to the `unet` for generation. If the type is + specified as `torch.FloatTensor`, it is passed to ControlNet as is. `PIL.Image.Image` can also be + accepted as an image. The dimensions of the output image defaults to `image`'s dimensions. If height + and/or width are passed, `image` is resized accordingly. If multiple ControlNets are specified in + `init`, images must be passed as a list such that each element of the list can be correctly batched for + input to a single ControlNet. + height (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`): + The height in pixels of the generated image. + width (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`): + The width in pixels of the generated image. + num_inference_steps (`int`, *optional*, defaults to 50): + The number of denoising steps. More denoising steps usually lead to a higher quality image at the + expense of slower inference. + timesteps (`List[int]`, *optional*): + Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument + in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is + passed will be used. Must be in descending order. + guidance_scale (`float`, *optional*, defaults to 7.5): + A higher guidance scale value encourages the model to generate images closely linked to the text + `prompt` at the expense of lower image quality. Guidance scale is enabled when `guidance_scale > 1`. + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide what to not include in image generation. If not defined, you need to + pass `negative_prompt_embeds` instead. Ignored when not using guidance (`guidance_scale < 1`). + num_images_per_prompt (`int`, *optional*, defaults to 1): + The number of images to generate per prompt. + eta (`float`, *optional*, defaults to 0.0): + Corresponds to parameter eta (η) from the [DDIM](https://arxiv.org/abs/2010.02502) paper. Only applies + to the [`~schedulers.DDIMScheduler`], and is ignored in other schedulers. + generator (`torch.Generator` or `List[torch.Generator]`, *optional*): + A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make + generation deterministic. + latents (`torch.FloatTensor`, *optional*): + Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for image + generation. Can be used to tweak the same generation with different prompts. If not provided, a latents + tensor is generated by sampling using the supplied random `generator`. + prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs (prompt weighting). If not + provided, text embeddings are generated from the `prompt` input argument. + negative_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs (prompt weighting). If + not provided, `negative_prompt_embeds` are generated from the `negative_prompt` input argument. + ip_adapter_image: (`PipelineImageInput`, *optional*): Optional image input to work with IP Adapters. + output_type (`str`, *optional*, defaults to `"pil"`): + The output format of the generated image. Choose between `PIL.Image` or `np.array`. + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] instead of a + plain tuple. + callback (`Callable`, *optional*): + A function that calls every `callback_steps` steps during inference. The function is called with the + following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`. + callback_steps (`int`, *optional*, defaults to 1): + The frequency at which the `callback` function is called. If not specified, the callback is called at + every step. + cross_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the [`AttentionProcessor`] as defined in + [`self.processor`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + controlnet_conditioning_scale (`float` or `List[float]`, *optional*, defaults to 1.0): + The outputs of the ControlNet are multiplied by `controlnet_conditioning_scale` before they are added + to the residual in the original `unet`. If multiple ControlNets are specified in `init`, you can set + the corresponding scale as a list. + guess_mode (`bool`, *optional*, defaults to `False`): + The ControlNet encoder tries to recognize the content of the input image even if you remove all + prompts. A `guidance_scale` value between 3.0 and 5.0 is recommended. + control_guidance_start (`float` or `List[float]`, *optional*, defaults to 0.0): + The percentage of total steps at which the ControlNet starts applying. + control_guidance_end (`float` or `List[float]`, *optional*, defaults to 1.0): + The percentage of total steps at which the ControlNet stops applying. + clip_skip (`int`, *optional*): + Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that + the output of the pre-final layer will be used for computing the prompt embeddings. + callback_on_step_end (`Callable`, *optional*): + A function that calls at the end of each denoising steps during the inference. The function is called + with the following arguments: `callback_on_step_end(self: DiffusionPipeline, step: int, timestep: int, + callback_kwargs: Dict)`. `callback_kwargs` will include a list of all tensors as specified by + `callback_on_step_end_tensor_inputs`. + callback_on_step_end_tensor_inputs (`List`, *optional*): + The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list + will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the + `._callback_tensor_inputs` attribute of your pipeine class. + + Examples: + + Returns: + [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] or `tuple`: + If `return_dict` is `True`, [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] is returned, + otherwise a `tuple` is returned where the first element is a list with the generated images and the + second element is a list of `bool`s indicating whether the corresponding generated image contains + "not-safe-for-work" (nsfw) content. + """ + + callback = kwargs.pop("callback", None) + callback_steps = kwargs.pop("callback_steps", None) + + if callback is not None: + deprecate( + "callback", + "1.0.0", + "Passing `callback` as an input argument to `__call__` is deprecated, consider using `callback_on_step_end`", + ) + if callback_steps is not None: + deprecate( + "callback_steps", + "1.0.0", + "Passing `callback_steps` as an input argument to `__call__` is deprecated, consider using `callback_on_step_end`", + ) + + controlnet = self.controlnet._orig_mod if is_compiled_module(self.controlnet) else self.controlnet + + # align format for control guidance + if not isinstance(control_guidance_start, list) and isinstance(control_guidance_end, list): + control_guidance_start = len(control_guidance_end) * [control_guidance_start] + elif not isinstance(control_guidance_end, list) and isinstance(control_guidance_start, list): + control_guidance_end = len(control_guidance_start) * [control_guidance_end] + elif not isinstance(control_guidance_start, list) and not isinstance(control_guidance_end, list): + mult = len(controlnet.nets) if isinstance(controlnet, MultiControlNetModel) else 1 + control_guidance_start, control_guidance_end = ( + mult * [control_guidance_start], + mult * [control_guidance_end], + ) + + # 1. Check inputs. Raise error if not correct + self.check_inputs( + prompt, + image, + callback_steps, + negative_prompt, + prompt_embeds, + negative_prompt_embeds, + controlnet_conditioning_scale, + control_guidance_start, + control_guidance_end, + callback_on_step_end_tensor_inputs, + ) + + self._guidance_scale = guidance_scale + self._clip_skip = clip_skip + self._cross_attention_kwargs = cross_attention_kwargs + + # 2. Define call parameters + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + device = self._execution_device + + if isinstance(controlnet, MultiControlNetModel) and isinstance(controlnet_conditioning_scale, float): + controlnet_conditioning_scale = [controlnet_conditioning_scale] * len(controlnet.nets) + + global_pool_conditions = ( + controlnet.config.global_pool_conditions + if isinstance(controlnet, ControlNetModel) + else controlnet.nets[0].config.global_pool_conditions + ) + guess_mode = guess_mode or global_pool_conditions + + # 3. Encode input prompt + text_encoder_lora_scale = ( + self.cross_attention_kwargs.get("scale", None) if self.cross_attention_kwargs is not None else None + ) + prompt_embeds, negative_prompt_embeds = self.encode_prompt( + prompt, + device, + num_images_per_prompt, + self.do_classifier_free_guidance, + negative_prompt, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + lora_scale=text_encoder_lora_scale, + clip_skip=self.clip_skip, + ) + # For classifier free guidance, we need to do two forward passes. + # Here we concatenate the unconditional and text embeddings into a single batch + # to avoid doing two forward passes + if self.do_classifier_free_guidance: + prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds]) + + if ip_adapter_image is not None or ip_adapter_image_embeds is not None: + image_embeds = self.prepare_ip_adapter_image_embeds( + ip_adapter_image, + ip_adapter_image_embeds, + device, + batch_size * num_images_per_prompt, + self.do_classifier_free_guidance, + ) + + # 4. Prepare image + if isinstance(controlnet, ControlNetModel): + image = self.prepare_image( + image=image, + width=width, + height=height, + batch_size=batch_size * num_images_per_prompt, + num_images_per_prompt=num_images_per_prompt, + device=device, + dtype=controlnet.dtype, + do_classifier_free_guidance=self.do_classifier_free_guidance, + guess_mode=guess_mode, + ) + height, width = image.shape[-2:] + elif isinstance(controlnet, MultiControlNetModel): + images = [] + + for image_ in image: + image_ = self.prepare_image( + image=image_, + width=width, + height=height, + batch_size=batch_size * num_images_per_prompt, + num_images_per_prompt=num_images_per_prompt, + device=device, + dtype=controlnet.dtype, + do_classifier_free_guidance=self.do_classifier_free_guidance, + guess_mode=guess_mode, + ) + + images.append(image_) + + image = images + height, width = image[0].shape[-2:] + else: + assert False + + # 5. Prepare timesteps + timesteps, num_inference_steps = retrieve_timesteps(self.scheduler, num_inference_steps, device, timesteps) + self._num_timesteps = len(timesteps) + + # 6. Prepare latent variables + num_channels_latents = self.unet.config.in_channels + latents = self.prepare_latents( + batch_size * num_images_per_prompt, + num_channels_latents, + height, + width, + prompt_embeds.dtype, + device, + generator, + latents, + ) + + # 6.5 Optionally get Guidance Scale Embedding + timestep_cond = None + if self.unet.config.time_cond_proj_dim is not None: + guidance_scale_tensor = torch.tensor(self.guidance_scale - 1).repeat(batch_size * num_images_per_prompt) + timestep_cond = self.get_guidance_scale_embedding( + guidance_scale_tensor, embedding_dim=self.unet.config.time_cond_proj_dim + ).to(device=device, dtype=latents.dtype) + + # 7. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline + extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) + + # 7.1 Add image embeds for IP-Adapter + added_cond_kwargs = {"image_embeds": image_embeds} if image_embeds is not None else None + + # 7.2 Create tensor stating which controlnets to keep + controlnet_keep = [] + for i in range(len(timesteps)): + keeps = [ + 1.0 - float(i / len(timesteps) < s or (i + 1) / len(timesteps) > e) + for s, e in zip(control_guidance_start, control_guidance_end) + ] + controlnet_keep.append(keeps[0] if isinstance(controlnet, ControlNetModel) else keeps) + + # 8. Denoising loop + num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order + is_unet_compiled = is_compiled_module(self.unet) + is_controlnet_compiled = is_compiled_module(self.controlnet) + is_torch_higher_equal_2_1 = is_torch_version(">=", "2.1") + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + # Relevant thread: + # https://dev-discuss.pytorch.org/t/cudagraphs-in-pytorch-2-0/1428 + if (is_unet_compiled and is_controlnet_compiled) and is_torch_higher_equal_2_1: + torch._inductor.cudagraph_mark_step_begin() + # expand the latents if we are doing classifier free guidance + latent_model_input = torch.cat([latents] * 2) if self.do_classifier_free_guidance else latents + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + # controlnet(s) inference + if guess_mode and self.do_classifier_free_guidance: + # Infer ControlNet only for the conditional batch. + control_model_input = latents + control_model_input = self.scheduler.scale_model_input(control_model_input, t) + controlnet_prompt_embeds = prompt_embeds.chunk(2)[1] + else: + control_model_input = latent_model_input + controlnet_prompt_embeds = prompt_embeds + + if isinstance(controlnet_keep[i], list): + cond_scale = [c * s for c, s in zip(controlnet_conditioning_scale, controlnet_keep[i])] + else: + controlnet_cond_scale = controlnet_conditioning_scale + if isinstance(controlnet_cond_scale, list): + controlnet_cond_scale = controlnet_cond_scale[0] + cond_scale = controlnet_cond_scale * controlnet_keep[i] + + down_block_res_samples, mid_block_res_sample = self.controlnet( + control_model_input, + t, + encoder_hidden_states=controlnet_prompt_embeds, + controlnet_cond=image, + conditioning_scale=cond_scale, + guess_mode=guess_mode, + return_dict=False, + ) + + if guess_mode and self.do_classifier_free_guidance: + # Infered ControlNet only for the conditional batch. + # To apply the output of ControlNet to both the unconditional and conditional batches, + # add 0 to the unconditional batch to keep it unchanged. + down_block_res_samples = [torch.cat([torch.zeros_like(d), d]) for d in down_block_res_samples] + mid_block_res_sample = torch.cat([torch.zeros_like(mid_block_res_sample), mid_block_res_sample]) + + # predict the noise residual + noise_pred = self.unet( + latent_model_input, + t, + encoder_hidden_states=prompt_embeds, + timestep_cond=timestep_cond, + cross_attention_kwargs=self.cross_attention_kwargs, + down_block_additional_residuals=down_block_res_samples, + mid_block_additional_residual=mid_block_res_sample, + added_cond_kwargs=added_cond_kwargs, + return_dict=False, + )[0] + + # perform guidance + if self.do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) + noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond) + + # compute the previous noisy sample x_t -> x_t-1 + latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] + + if callback_on_step_end is not None: + callback_kwargs = {} + for k in callback_on_step_end_tensor_inputs: + callback_kwargs[k] = locals()[k] + callback_outputs = callback_on_step_end(self, i, t, callback_kwargs) + + latents = callback_outputs.pop("latents", latents) + prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds) + negative_prompt_embeds = callback_outputs.pop("negative_prompt_embeds", negative_prompt_embeds) + + # call the callback, if provided + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + if callback is not None and i % callback_steps == 0: + step_idx = i // getattr(self.scheduler, "order", 1) + callback(step_idx, t, latents) + + # If we do sequential model offloading, let's offload unet and controlnet + # manually for max memory savings + if hasattr(self, "final_offload_hook") and self.final_offload_hook is not None: + self.unet.to("cpu") + self.controlnet.to("cpu") + torch.cuda.empty_cache() + + if not output_type == "latent": + image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False, generator=generator)[ + 0 + ] + image, has_nsfw_concept = self.run_safety_checker(image, device, prompt_embeds.dtype) + else: + image = latents + has_nsfw_concept = None + + if has_nsfw_concept is None: + do_denormalize = [True] * image.shape[0] + else: + do_denormalize = [not has_nsfw for has_nsfw in has_nsfw_concept] + + image = self.image_processor.postprocess(image, output_type=output_type, do_denormalize=do_denormalize) + + # Offload all models + self.maybe_free_model_hooks() + + if not return_dict: + return (image, has_nsfw_concept) + + return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept) diff --git a/foleycrafter/utils/audio_to_mel_af.py b/foleycrafter/utils/audio_to_mel_af.py new file mode 100644 index 0000000000000000000000000000000000000000..e0335eba4637457ca78ff5990f86b085bef49f59 --- /dev/null +++ b/foleycrafter/utils/audio_to_mel_af.py @@ -0,0 +1,181 @@ +import numpy as np +from PIL import Image + +import math +import os +import random +import torch +import json +import torch.utils.data +import numpy as np +import librosa +from librosa.util import normalize +from scipy.io.wavfile import read +from librosa.filters import mel as librosa_mel_fn + +import torch.nn.functional as F +import torch.nn as nn +from torch.nn import Conv1d, ConvTranspose1d, AvgPool1d, Conv2d +from torch.nn.utils import weight_norm, remove_weight_norm, spectral_norm + +MAX_WAV_VALUE = 32768.0 + + +def load_wav(full_path): + sampling_rate, data = read(full_path) + return data, sampling_rate + + +def dynamic_range_compression(x, C=1, clip_val=1e-5): + return np.log(np.clip(x, a_min=clip_val, a_max=None) * C) + + +def dynamic_range_decompression(x, C=1): + return np.exp(x) / C + + +def dynamic_range_compression_torch(x, C=1, clip_val=1e-5): + return torch.log(torch.clamp(x, min=clip_val) * C) + + +def dynamic_range_decompression_torch(x, C=1): + return torch.exp(x) / C + + +def spectral_normalize_torch(magnitudes): + output = dynamic_range_compression_torch(magnitudes) + return output + + +def spectral_de_normalize_torch(magnitudes): + output = dynamic_range_decompression_torch(magnitudes) + return output + + +mel_basis = {} +hann_window = {} + + +def mel_spectrogram(y, n_fft, num_mels, sampling_rate, hop_size, win_size, fmin, fmax, center=False): + if torch.min(y) < -1.: + print('min value is ', torch.min(y)) + if torch.max(y) > 1.: + print('max value is ', torch.max(y)) + + global mel_basis, hann_window + if fmax not in mel_basis: + mel = librosa_mel_fn(sr=sampling_rate, n_fft=n_fft, n_mels=num_mels, fmin=fmin, fmax=fmax) + mel_basis[str(fmax)+'_'+str(y.device)] = torch.from_numpy(mel).float().to(y.device) + hann_window[str(y.device)] = torch.hann_window(win_size).to(y.device) + + y = torch.nn.functional.pad(y.unsqueeze(1), (int((n_fft-hop_size)/2), int((n_fft-hop_size)/2)), mode='reflect') + y = y.squeeze(1) + + # complex tensor as default, then use view_as_real for future pytorch compatibility + spec = torch.stft(y, n_fft, hop_length=hop_size, win_length=win_size, window=hann_window[str(y.device)], + center=center, pad_mode='reflect', normalized=False, onesided=True, return_complex=True) + spec = torch.view_as_real(spec) + spec = torch.sqrt(spec.pow(2).sum(-1)+(1e-9)) + + spec = torch.matmul(mel_basis[str(fmax)+'_'+str(y.device)], spec) + spec = spectral_normalize_torch(spec) + + return spec + + +def spectrogram(y, n_fft, num_mels, sampling_rate, hop_size, win_size, fmin, fmax, center=False): + if torch.min(y) < -1.: + print('min value is ', torch.min(y)) + if torch.max(y) > 1.: + print('max value is ', torch.max(y)) + + global hann_window + hann_window[str(y.device)] = torch.hann_window(win_size).to(y.device) + + y = torch.nn.functional.pad(y.unsqueeze(1), (int((n_fft-hop_size)/2), int((n_fft-hop_size)/2)), mode='reflect') + y = y.squeeze(1) + + # complex tensor as default, then use view_as_real for future pytorch compatibility + spec = torch.stft(y, n_fft, hop_length=hop_size, win_length=win_size, window=hann_window[str(y.device)], + center=center, pad_mode='reflect', normalized=False, onesided=True, return_complex=True) + spec = torch.view_as_real(spec) + spec = torch.sqrt(spec.pow(2).sum(-1)+(1e-9)) + + return spec + + +def normalize_spectrogram( + spectrogram: torch.Tensor, + max_value: float = 200, + min_value: float = 1e-5, + power: float = 1., + inverse: bool = False +) -> torch.Tensor: + # Rescale to 0-1 + max_value = np.log(max_value) # 5.298317366548036 + min_value = np.log(min_value) # -11.512925464970229 + + assert spectrogram.max() <= max_value and spectrogram.min() >= min_value + + data = (spectrogram - min_value) / (max_value - min_value) + + # Invert + if inverse: + data = 1 - data + + # Apply the power curve + data = torch.pow(data, power) + + # 1D -> 3D + data = data.unsqueeze(1) + # data = data.repeat(1, 3, 1, 1) + # (b f) (h w) c -> b f (h w) c -> b t (h w) c -> b t (h' w') c + + # Flip Y axis: image origin at the top-left corner, spectrogram origin at the bottom-left corner + data = torch.flip(data, [1]) + + return data + +def denormalize_spectrogram( + data: torch.Tensor, + max_value: float = 200, + min_value: float = 1e-5, + power: float = 1, + inverse: bool = False, +) -> torch.Tensor: + + max_value = np.log(max_value) + min_value = np.log(min_value) + + # Flip Y axis: image origin at the top-left corner, spectrogram origin at the bottom-left corner + data = torch.flip(data, [1]) + + assert len(data.shape) == 3, "Expected 3 dimensions, got {}".format(len(data.shape)) + + if data.shape[0] == 1: + data = data.repeat(3, 1, 1) + + assert data.shape[0] == 3, "Expected 3 channels, got {}".format(data.shape[0]) + data = data[0] + + # Reverse the power curve + data = torch.pow(data, 1 / power) + + # Invert + if inverse: + data = 1 - data + + # Rescale to max value + spectrogram = data * (max_value - min_value) + min_value + + return spectrogram + + +def get_mel_spectrogram_from_audio(audio): + # for auffusion + spec = mel_spectrogram(audio, n_fft=2048, num_mels=256, sampling_rate=16000, hop_size=160, win_size=1024, fmin=0, fmax=8000, center=False) + + # for audioldm + # spec = mel_spectrogram(audio, n_fft=1024, num_mels=64, sampling_rate=16000, hop_size=160, win_size=1024, fmin=0, fmax=8000, center=False) + spec = normalize_spectrogram(spec) + return spec \ No newline at end of file diff --git a/foleycrafter/utils/converter.py b/foleycrafter/utils/converter.py new file mode 100644 index 0000000000000000000000000000000000000000..7ecfaa22c7f17e024b7b7d0e142f4ab5785e13eb --- /dev/null +++ b/foleycrafter/utils/converter.py @@ -0,0 +1,398 @@ +# Copy from https://github.com/happylittlecat2333/Auffusion/blob/main/converter.py +import numpy as np +from PIL import Image + +import math +import os +import random +import torch +import json +import torch.utils.data +import numpy as np +import librosa +# from librosa.util import normalize +from scipy.io.wavfile import read +from librosa.filters import mel as librosa_mel_fn + +import torch.nn.functional as F +import torch.nn as nn +from torch.nn import Conv1d, ConvTranspose1d, AvgPool1d, Conv2d +from torch.nn.utils import weight_norm, remove_weight_norm, spectral_norm + +MAX_WAV_VALUE = 32768.0 + + +def load_wav(full_path): + sampling_rate, data = read(full_path) + return data, sampling_rate + + +def dynamic_range_compression(x, C=1, clip_val=1e-5): + return np.log(np.clip(x, a_min=clip_val, a_max=None) * C) + + +def dynamic_range_decompression(x, C=1): + return np.exp(x) / C + + +def dynamic_range_compression_torch(x, C=1, clip_val=1e-5): + return torch.log(torch.clamp(x, min=clip_val) * C) + + +def dynamic_range_decompression_torch(x, C=1): + return torch.exp(x) / C + + +def spectral_normalize_torch(magnitudes): + output = dynamic_range_compression_torch(magnitudes) + return output + + +def spectral_de_normalize_torch(magnitudes): + output = dynamic_range_decompression_torch(magnitudes) + return output + + +mel_basis = {} +hann_window = {} + + +def mel_spectrogram(y, n_fft, num_mels, sampling_rate, hop_size, win_size, fmin, fmax, center=False): + if torch.min(y) < -1.: + print('min value is ', torch.min(y)) + if torch.max(y) > 1.: + print('max value is ', torch.max(y)) + + global mel_basis, hann_window + if fmax not in mel_basis: + mel = librosa_mel_fn(sr=sampling_rate, n_fft=n_fft, n_mels=num_mels, fmin=fmin, fmax=fmax) + mel_basis[str(fmax)+'_'+str(y.device)] = torch.from_numpy(mel).float().to(y.device) + hann_window[str(y.device)] = torch.hann_window(win_size).to(y.device) + + y = torch.nn.functional.pad(y.unsqueeze(1), (int((n_fft-hop_size)/2), int((n_fft-hop_size)/2)), mode='reflect') + y = y.squeeze(1) + + # complex tensor as default, then use view_as_real for future pytorch compatibility + spec = torch.stft(y, n_fft, hop_length=hop_size, win_length=win_size, window=hann_window[str(y.device)], + center=center, pad_mode='reflect', normalized=False, onesided=True, return_complex=True) + spec = torch.view_as_real(spec) + spec = torch.sqrt(spec.pow(2).sum(-1)+(1e-9)) + + spec = torch.matmul(mel_basis[str(fmax)+'_'+str(y.device)], spec) + spec = spectral_normalize_torch(spec) + + return spec + + +def spectrogram(y, n_fft, num_mels, sampling_rate, hop_size, win_size, fmin, fmax, center=False): + if torch.min(y) < -1.: + print('min value is ', torch.min(y)) + if torch.max(y) > 1.: + print('max value is ', torch.max(y)) + + global hann_window + hann_window[str(y.device)] = torch.hann_window(win_size).to(y.device) + + y = torch.nn.functional.pad(y.unsqueeze(1), (int((n_fft-hop_size)/2), int((n_fft-hop_size)/2)), mode='reflect') + y = y.squeeze(1) + + # complex tensor as default, then use view_as_real for future pytorch compatibility + spec = torch.stft(y, n_fft, hop_length=hop_size, win_length=win_size, window=hann_window[str(y.device)], + center=center, pad_mode='reflect', normalized=False, onesided=True, return_complex=True) + spec = torch.view_as_real(spec) + spec = torch.sqrt(spec.pow(2).sum(-1)+(1e-9)) + + return spec + + +def normalize_spectrogram( + spectrogram: torch.Tensor, + max_value: float = 200, + min_value: float = 1e-5, + power: float = 1., + inverse: bool = False +) -> torch.Tensor: + + # Rescale to 0-1 + max_value = np.log(max_value) # 5.298317366548036 + min_value = np.log(min_value) # -11.512925464970229 + + assert spectrogram.max() <= max_value and spectrogram.min() >= min_value + + data = (spectrogram - min_value) / (max_value - min_value) + + # Invert + if inverse: + data = 1 - data + + # Apply the power curve + data = torch.pow(data, power) + + # 1D -> 3D + data = data.repeat(3, 1, 1) + + # Flip Y axis: image origin at the top-left corner, spectrogram origin at the bottom-left corner + data = torch.flip(data, [1]) + + return data + + + +def denormalize_spectrogram( + data: torch.Tensor, + max_value: float = 200, + min_value: float = 1e-5, + power: float = 1, + inverse: bool = False, +) -> torch.Tensor: + + max_value = np.log(max_value) + min_value = np.log(min_value) + + # Flip Y axis: image origin at the top-left corner, spectrogram origin at the bottom-left corner + data = torch.flip(data, [1]) + + assert len(data.shape) == 3, "Expected 3 dimensions, got {}".format(len(data.shape)) + + if data.shape[0] == 1: + data = data.repeat(3, 1, 1) + + assert data.shape[0] == 3, "Expected 3 channels, got {}".format(data.shape[0]) + data = data[0] + + # Reverse the power curve + data = torch.pow(data, 1 / power) + + # Invert + if inverse: + data = 1 - data + + # Rescale to max value + spectrogram = data * (max_value - min_value) + min_value + + return spectrogram + + +def get_mel_spectrogram_from_audio(audio, device="cpu"): + audio = audio / MAX_WAV_VALUE + audio = librosa.util.normalize(audio) * 0.95 + # print(' >>> normalize done <<< ') + + audio = torch.FloatTensor(audio) + audio = audio.unsqueeze(0) + + waveform = audio.to(device) + spec = mel_spectrogram(waveform, n_fft=2048, num_mels=256, sampling_rate=16000, hop_size=160, win_size=1024, fmin=0, fmax=8000, center=False) + return audio, spec + + + +LRELU_SLOPE = 0.1 +MAX_WAV_VALUE = 32768.0 + + +class AttrDict(dict): + def __init__(self, *args, **kwargs): + super(AttrDict, self).__init__(*args, **kwargs) + self.__dict__ = self + + +def get_config(config_path): + config = json.loads(open(config_path).read()) + config = AttrDict(config) + return config + +def init_weights(m, mean=0.0, std=0.01): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + m.weight.data.normal_(mean, std) + + +def apply_weight_norm(m): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + weight_norm(m) + + +def get_padding(kernel_size, dilation=1): + return int((kernel_size*dilation - dilation)/2) + + +class ResBlock1(torch.nn.Module): + def __init__(self, h, channels, kernel_size=3, dilation=(1, 3, 5)): + super(ResBlock1, self).__init__() + self.h = h + self.convs1 = nn.ModuleList([ + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=dilation[0], + padding=get_padding(kernel_size, dilation[0]))), + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=dilation[1], + padding=get_padding(kernel_size, dilation[1]))), + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=dilation[2], + padding=get_padding(kernel_size, dilation[2]))) + ]) + self.convs1.apply(init_weights) + + self.convs2 = nn.ModuleList([ + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=1, + padding=get_padding(kernel_size, 1))), + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=1, + padding=get_padding(kernel_size, 1))), + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=1, + padding=get_padding(kernel_size, 1))) + ]) + self.convs2.apply(init_weights) + + def forward(self, x): + for c1, c2 in zip(self.convs1, self.convs2): + xt = F.leaky_relu(x, LRELU_SLOPE) + xt = c1(xt) + xt = F.leaky_relu(xt, LRELU_SLOPE) + xt = c2(xt) + x = xt + x + return x + + def remove_weight_norm(self): + for l in self.convs1: + remove_weight_norm(l) + for l in self.convs2: + remove_weight_norm(l) + + +class ResBlock2(torch.nn.Module): + def __init__(self, h, channels, kernel_size=3, dilation=(1, 3)): + super(ResBlock2, self).__init__() + self.h = h + self.convs = nn.ModuleList([ + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=dilation[0], + padding=get_padding(kernel_size, dilation[0]))), + weight_norm(Conv1d(channels, channels, kernel_size, 1, dilation=dilation[1], + padding=get_padding(kernel_size, dilation[1]))) + ]) + self.convs.apply(init_weights) + + def forward(self, x): + for c in self.convs: + xt = F.leaky_relu(x, LRELU_SLOPE) + xt = c(xt) + x = xt + x + return x + + def remove_weight_norm(self): + for l in self.convs: + remove_weight_norm(l) + + + +class Generator(torch.nn.Module): + def __init__(self, h): + super(Generator, self).__init__() + self.h = h + self.num_kernels = len(h.resblock_kernel_sizes) + self.num_upsamples = len(h.upsample_rates) + self.conv_pre = weight_norm(Conv1d(h.num_mels, h.upsample_initial_channel, 7, 1, padding=3)) # change: 80 --> 512 + resblock = ResBlock1 if h.resblock == '1' else ResBlock2 + + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(h.upsample_rates, h.upsample_kernel_sizes)): + if (k-u) % 2 == 0: + self.ups.append(weight_norm( + ConvTranspose1d(h.upsample_initial_channel//(2**i), h.upsample_initial_channel//(2**(i+1)), + k, u, padding=(k-u)//2))) + else: + self.ups.append(weight_norm( + ConvTranspose1d(h.upsample_initial_channel//(2**i), h.upsample_initial_channel//(2**(i+1)), + k, u, padding=(k-u)//2+1, output_padding=1))) + + # self.ups.append(weight_norm( + # ConvTranspose1d(h.upsample_initial_channel//(2**i), h.upsample_initial_channel//(2**(i+1)), + # k, u, padding=(k-u)//2))) + + + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = h.upsample_initial_channel//(2**(i+1)) + for j, (k, d) in enumerate(zip(h.resblock_kernel_sizes, h.resblock_dilation_sizes)): + self.resblocks.append(resblock(h, ch, k, d)) + + self.conv_post = weight_norm(Conv1d(ch, 1, 7, 1, padding=3)) + self.ups.apply(init_weights) + self.conv_post.apply(init_weights) + + def forward(self, x): + x = self.conv_pre(x) + for i in range(self.num_upsamples): + x = F.leaky_relu(x, LRELU_SLOPE) + x = self.ups[i](x) + xs = None + for j in range(self.num_kernels): + if xs is None: + xs = self.resblocks[i*self.num_kernels+j](x) + else: + xs += self.resblocks[i*self.num_kernels+j](x) + x = xs / self.num_kernels + x = F.leaky_relu(x) + x = self.conv_post(x) + x = torch.tanh(x) + + return x + + def remove_weight_norm(self): + for l in self.ups: + remove_weight_norm(l) + for l in self.resblocks: + l.remove_weight_norm() + remove_weight_norm(self.conv_pre) + remove_weight_norm(self.conv_post) + + @classmethod + def from_pretrained(cls, pretrained_model_name_or_path, subfolder=None): + if subfolder is not None: + pretrained_model_name_or_path = os.path.join(pretrained_model_name_or_path, subfolder) + config_path = os.path.join(pretrained_model_name_or_path, "config.json") + ckpt_path = os.path.join(pretrained_model_name_or_path, "vocoder.pt") + + config = get_config(config_path) + vocoder = cls(config) + + state_dict_g = torch.load(ckpt_path) + vocoder.load_state_dict(state_dict_g["generator"]) + vocoder.eval() + vocoder.remove_weight_norm() + return vocoder + + + @torch.no_grad() + def inference(self, mels, lengths=None): + self.eval() + with torch.no_grad(): + wavs = self(mels).squeeze(1) + + wavs = (wavs.cpu().numpy() * MAX_WAV_VALUE).astype("int16") + + if lengths is not None: + wavs = wavs[:, :lengths] + + return wavs + +def normalize(images): + """ + Normalize an image array to [-1,1]. + """ + if images.min() >= 0: + return 2.0 * images - 1.0 + else: + return images + +def pad_spec(spec, spec_length, pad_value=0, random_crop=True): # spec: [3, mel_dim, spec_len] + assert spec_length % 8 == 0, "spec_length must be divisible by 8" + if spec.shape[-1] < spec_length: + # pad spec to spec_length + spec = F.pad(spec, (0, spec_length - spec.shape[-1]), value=pad_value) + else: + # random crop + if random_crop: + start = random.randint(0, spec.shape[-1] - spec_length) + spec = spec[:, :, start:start+spec_length] + else: + spec = spec[:, :, :spec_length] + return spec \ No newline at end of file diff --git a/foleycrafter/utils/spec_to_mel.py b/foleycrafter/utils/spec_to_mel.py new file mode 100644 index 0000000000000000000000000000000000000000..b77358dd8ae5af3473da0c8f25834ab2c2596a27 --- /dev/null +++ b/foleycrafter/utils/spec_to_mel.py @@ -0,0 +1,403 @@ +import torch +import torchaudio +import torch.nn.functional as F +import numpy as np +from scipy.signal import get_window +import librosa.util as librosa_util +from librosa.util import pad_center, tiny +from librosa.filters import mel as librosa_mel_fn +import io +# spectrogram to mel + +class STFT(torch.nn.Module): + """adapted from Prem Seetharaman's https://github.com/pseeth/pytorch-stft""" + + def __init__(self, filter_length, hop_length, win_length, window="hann"): + super(STFT, self).__init__() + self.filter_length = filter_length + self.hop_length = hop_length + self.win_length = win_length + self.window = window + self.forward_transform = None + scale = self.filter_length / self.hop_length + fourier_basis = np.fft.fft(np.eye(self.filter_length)) + + cutoff = int((self.filter_length / 2 + 1)) + fourier_basis = np.vstack( + [np.real(fourier_basis[:cutoff, :]), np.imag(fourier_basis[:cutoff, :])] + ) + + forward_basis = torch.FloatTensor(fourier_basis[:, None, :]) + inverse_basis = torch.FloatTensor( + np.linalg.pinv(scale * fourier_basis).T[:, None, :] + ) + + if window is not None: + assert filter_length >= win_length + # get window and zero center pad it to filter_length + fft_window = get_window(window, win_length, fftbins=True) + fft_window = pad_center(fft_window, filter_length) + fft_window = torch.from_numpy(fft_window).float() + + # window the bases + forward_basis *= fft_window + inverse_basis *= fft_window + + self.register_buffer("forward_basis", forward_basis.float()) + self.register_buffer("inverse_basis", inverse_basis.float()) + + def transform(self, input_data): + num_batches = input_data.size(0) + num_samples = input_data.size(1) + + self.num_samples = num_samples + + # similar to librosa, reflect-pad the input + input_data = input_data.view(num_batches, 1, num_samples) + input_data = F.pad( + input_data.unsqueeze(1), + (int(self.filter_length / 2), int(self.filter_length / 2), 0, 0), + mode="reflect", + ) + input_data = input_data.squeeze(1) + + forward_transform = F.conv1d( + input_data, + torch.autograd.Variable(self.forward_basis, requires_grad=False), + stride=self.hop_length, + padding=0, + ).cpu() + + cutoff = int((self.filter_length / 2) + 1) + real_part = forward_transform[:, :cutoff, :] + imag_part = forward_transform[:, cutoff:, :] + + magnitude = torch.sqrt(real_part**2 + imag_part**2) + phase = torch.autograd.Variable(torch.atan2(imag_part.data, real_part.data)) + + return magnitude, phase + + def inverse(self, magnitude, phase): + recombine_magnitude_phase = torch.cat( + [magnitude * torch.cos(phase), magnitude * torch.sin(phase)], dim=1 + ) + + inverse_transform = F.conv_transpose1d( + recombine_magnitude_phase, + torch.autograd.Variable(self.inverse_basis, requires_grad=False), + stride=self.hop_length, + padding=0, + ) + + if self.window is not None: + window_sum = window_sumsquare( + self.window, + magnitude.size(-1), + hop_length=self.hop_length, + win_length=self.win_length, + n_fft=self.filter_length, + dtype=np.float32, + ) + # remove modulation effects + approx_nonzero_indices = torch.from_numpy( + np.where(window_sum > tiny(window_sum))[0] + ) + window_sum = torch.autograd.Variable( + torch.from_numpy(window_sum), requires_grad=False + ) + window_sum = window_sum + inverse_transform[:, :, approx_nonzero_indices] /= window_sum[ + approx_nonzero_indices + ] + + # scale by hop ratio + inverse_transform *= float(self.filter_length) / self.hop_length + + inverse_transform = inverse_transform[:, :, int(self.filter_length / 2) :] + inverse_transform = inverse_transform[:, :, : -int(self.filter_length / 2) :] + + return inverse_transform + + def forward(self, input_data): + self.magnitude, self.phase = self.transform(input_data) + reconstruction = self.inverse(self.magnitude, self.phase) + return reconstruction + +def window_sumsquare( + window, + n_frames, + hop_length, + win_length, + n_fft, + dtype=np.float32, + norm=None, +): + """ + # from librosa 0.6 + Compute the sum-square envelope of a window function at a given hop length. + + This is used to estimate modulation effects induced by windowing + observations in short-time fourier transforms. + + Parameters + ---------- + window : string, tuple, number, callable, or list-like + Window specification, as in `get_window` + + n_frames : int > 0 + The number of analysis frames + + hop_length : int > 0 + The number of samples to advance between frames + + win_length : [optional] + The length of the window function. By default, this matches `n_fft`. + + n_fft : int > 0 + The length of each analysis frame. + + dtype : np.dtype + The data type of the output + + Returns + ------- + wss : np.ndarray, shape=`(n_fft + hop_length * (n_frames - 1))` + The sum-squared envelope of the window function + """ + if win_length is None: + win_length = n_fft + + n = n_fft + hop_length * (n_frames - 1) + x = np.zeros(n, dtype=dtype) + + # Compute the squared window at the desired length + win_sq = get_window(window, win_length, fftbins=True) + win_sq = librosa_util.normalize(win_sq, norm=norm) ** 2 + win_sq = librosa_util.pad_center(win_sq, n_fft) + + # Fill the envelope + for i in range(n_frames): + sample = i * hop_length + x[sample : min(n, sample + n_fft)] += win_sq[: max(0, min(n_fft, n - sample))] + return x + + +def griffin_lim(magnitudes, stft_fn, n_iters=30): + """ + PARAMS + ------ + magnitudes: spectrogram magnitudes + stft_fn: STFT class with transform (STFT) and inverse (ISTFT) methods + """ + + angles = np.angle(np.exp(2j * np.pi * np.random.rand(*magnitudes.size()))) + angles = angles.astype(np.float32) + angles = torch.autograd.Variable(torch.from_numpy(angles)) + signal = stft_fn.inverse(magnitudes, angles).squeeze(1) + + for i in range(n_iters): + _, angles = stft_fn.transform(signal) + signal = stft_fn.inverse(magnitudes, angles).squeeze(1) + return signal + +def dynamic_range_compression(x, normalize_fun=torch.log, C=1, clip_val=1e-5): + """ + PARAMS + ------ + C: compression factor + """ + return normalize_fun(torch.clamp(x, min=clip_val) * C) + + +def dynamic_range_decompression(x, C=1): + """ + PARAMS + ------ + C: compression factor used to compress + """ + return torch.exp(x) / C +class TacotronSTFT(torch.nn.Module): + def __init__( + self, + filter_length, + hop_length, + win_length, + n_mel_channels, + sampling_rate, + mel_fmin, + mel_fmax, + ): + super(TacotronSTFT, self).__init__() + self.n_mel_channels = n_mel_channels + self.sampling_rate = sampling_rate + self.stft_fn = STFT(filter_length, hop_length, win_length) + mel_basis = librosa_mel_fn( + sampling_rate, filter_length, n_mel_channels, mel_fmin, mel_fmax + ) + mel_basis = torch.from_numpy(mel_basis).float() + self.register_buffer("mel_basis", mel_basis) + + def spectral_normalize(self, magnitudes, normalize_fun): + output = dynamic_range_compression(magnitudes, normalize_fun) + return output + + def spectral_de_normalize(self, magnitudes): + output = dynamic_range_decompression(magnitudes) + return output + + def mel_spectrogram(self, y, normalize_fun=torch.log): + """Computes mel-spectrograms from a batch of waves + PARAMS + ------ + y: Variable(torch.FloatTensor) with shape (B, T) in range [-1, 1] + + RETURNS + ------- + mel_output: torch.FloatTensor of shape (B, n_mel_channels, T) + """ + assert torch.min(y.data) >= -1, torch.min(y.data) + assert torch.max(y.data) <= 1, torch.max(y.data) + + magnitudes, phases = self.stft_fn.transform(y) + magnitudes = magnitudes.data + mel_output = torch.matmul(self.mel_basis, magnitudes) + mel_output = self.spectral_normalize(mel_output, normalize_fun) + energy = torch.norm(magnitudes, dim=1) + + log_magnitudes = self.spectral_normalize(magnitudes, normalize_fun) + + return mel_output, log_magnitudes, energy + +def pad_wav(waveform, segment_length): + waveform_length = waveform.shape[-1] + assert waveform_length > 100, "Waveform is too short, %s" % waveform_length + if segment_length is None or waveform_length == segment_length: + return waveform + elif waveform_length > segment_length: + return waveform[:,:segment_length] + elif waveform_length < segment_length: + temp_wav = np.zeros((1, segment_length)) + temp_wav[:, :waveform_length] = waveform + return temp_wav + +def normalize_wav(waveform): + waveform = waveform - np.mean(waveform) + waveform = waveform / (np.max(np.abs(waveform)) + 1e-8) + return waveform * 0.5 + +def _pad_spec(fbank, target_length=1024): + n_frames = fbank.shape[0] + p = target_length - n_frames + # cut and pad + if p > 0: + m = torch.nn.ZeroPad2d((0, 0, 0, p)) + fbank = m(fbank) + elif p < 0: + fbank = fbank[0:target_length, :] + + if fbank.size(-1) % 2 != 0: + fbank = fbank[..., :-1] + + return fbank + +def get_mel_from_wav(audio, _stft): + audio = torch.clip(torch.FloatTensor(audio).unsqueeze(0), -1, 1) + audio = torch.autograd.Variable(audio, requires_grad=False) + melspec, log_magnitudes_stft, energy = _stft.mel_spectrogram(audio) + melspec = torch.squeeze(melspec, 0).numpy().astype(np.float32) + log_magnitudes_stft = ( + torch.squeeze(log_magnitudes_stft, 0).numpy().astype(np.float32) + ) + energy = torch.squeeze(energy, 0).numpy().astype(np.float32) + return melspec, log_magnitudes_stft, energy + +def read_wav_file_io(bytes): + # waveform, sr = librosa.load(filename, sr=None, mono=True) # 4 times slower + waveform, sr = torchaudio.load(bytes, format='mp4') # Faster!!! + waveform = torchaudio.functional.resample(waveform, orig_freq=sr, new_freq=16000) + # waveform = waveform.numpy()[0, ...] + # waveform = normalize_wav(waveform) + # waveform = waveform[None, ...] + + # waveform = waveform / (np.max(np.abs(waveform)) + 1e-8) + # waveform = 0.5 * waveform + + return waveform + +def load_audio(bytes, sample_rate=16000): + waveform, sr = torchaudio.load(bytes, format='mp4') + waveform = torchaudio.functional.resample(waveform, orig_freq=sr, new_freq=sample_rate) + return waveform + +def read_wav_file(filename): + # waveform, sr = librosa.load(filename, sr=None, mono=True) # 4 times slower + waveform, sr = torchaudio.load(filename) # Faster!!! + waveform = torchaudio.functional.resample(waveform, orig_freq=sr, new_freq=16000) + waveform = waveform.numpy()[0, ...] + waveform = normalize_wav(waveform) + waveform = waveform[None, ...] + + waveform = waveform / np.max(np.abs(waveform)) + waveform = 0.5 * waveform + + return waveform + +def norm_wav_tensor(waveform: torch.FloatTensor): + waveform = waveform.numpy()[0, ...] + waveform = normalize_wav(waveform) + waveform = waveform[None, ...] + waveform = waveform / (np.max(np.abs(waveform)) + 1e-8) + waveform = 0.5 * waveform + return waveform + +def wav_to_fbank(filename, target_length=1024, fn_STFT=None): + if fn_STFT is None: + fn_STFT = TacotronSTFT( + 1024, # filter_length + 160, # hop_length + 1024, # win_length + 64, # n_mel + 16000, # sample_rate + 0, # fmin + 8000, # fmax + ) + + # mixup + waveform = read_wav_file(filename, target_length * 160) # hop size is 160 + + waveform = waveform[0, ...] + waveform = torch.FloatTensor(waveform) + + fbank, log_magnitudes_stft, energy = get_mel_from_wav(waveform, fn_STFT) + + fbank = torch.FloatTensor(fbank.T) + log_magnitudes_stft = torch.FloatTensor(log_magnitudes_stft.T) + + fbank, log_magnitudes_stft = _pad_spec(fbank, target_length), _pad_spec( + log_magnitudes_stft, target_length + ) + + return fbank, log_magnitudes_stft, waveform + +def wav_tensor_to_fbank(waveform, target_length=512, fn_STFT=None): + if fn_STFT is None: + fn_STFT = TacotronSTFT( + 1024, # filter_length + 160, # hop_length + 1024, # win_length + 256, # n_mel + 16000, # sample_rate + 0, # fmin + 8000, # fmax + ) # In practice used + + fbank, log_magnitudes_stft, energy = get_mel_from_wav(waveform, fn_STFT) + + fbank = torch.FloatTensor(fbank.T) + log_magnitudes_stft = torch.FloatTensor(log_magnitudes_stft.T) + + fbank, log_magnitudes_stft = _pad_spec(fbank, target_length), _pad_spec( + log_magnitudes_stft, target_length + ) + + return fbank \ No newline at end of file diff --git a/foleycrafter/utils/util.py b/foleycrafter/utils/util.py new file mode 100644 index 0000000000000000000000000000000000000000..cd135cc41f2f7619deed8cf58ac016eb02faa2ab --- /dev/null +++ b/foleycrafter/utils/util.py @@ -0,0 +1,1696 @@ +import torch +import torchvision +import torchaudio +import torchvision.transforms as transforms +from diffusers import UNet2DConditionModel, ControlNetModel +from foleycrafter.pipelines.pipeline_controlnet import StableDiffusionControlNetPipeline +from foleycrafter.pipelines.auffusion_pipeline import AuffusionNoAdapterPipeline, Generator +from foleycrafter.models.auffusion_unet import UNet2DConditionModel as af_UNet2DConditionModel +from diffusers.models import AutoencoderKLTemporalDecoder, AutoencoderKL +from diffusers.schedulers import EulerDiscreteScheduler, DDIMScheduler, PNDMScheduler, KarrasDiffusionSchedulers +from diffusers.utils.import_utils import is_xformers_available +from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection,\ + SpeechT5HifiGan, ClapTextModelWithProjection, RobertaTokenizer, RobertaTokenizerFast,\ + CLIPTextModel, CLIPTokenizer +import glob +from moviepy.editor import ImageSequenceClip, AudioFileClip, VideoFileClip, VideoClip +from moviepy.audio.AudioClip import AudioArrayClip +import numpy as np +from safetensors import safe_open +import random +from typing import Union, Optional +import decord +import os +import os.path as osp +import imageio +import soundfile as sf +from PIL import Image, ImageOps +import torch.distributed as dist +import io +from omegaconf import OmegaConf +import json + +from dataclasses import dataclass +from enum import Enum +import typing as T +import warnings +import pydub +from scipy.io import wavfile + +from einops import rearrange + +def zero_rank_print(s): + if (not dist.is_initialized()) or (dist.is_initialized() and dist.get_rank() == 0): print("### " + s, flush=True) + +def build_foleycrafter( + pretrained_model_name_or_path: str="auffusion/auffusion-full-no-adapter", +) -> StableDiffusionControlNetPipeline: + vae = AutoencoderKL.from_pretrained(pretrained_model_name_or_path, subfolder='vae') + unet = af_UNet2DConditionModel.from_pretrained(pretrained_model_name_or_path, subfolder='unet') + scheduler = PNDMScheduler.from_pretrained(pretrained_model_name_or_path, subfolder='scheduler') + tokenizer = CLIPTokenizer.from_pretrained(pretrained_model_name_or_path, subfolder='tokenizer') + text_encoder = CLIPTextModel.from_pretrained(pretrained_model_name_or_path, subfolder='text_encoder') + + controlnet = ControlNetModel.from_unet(unet, conditioning_channels=1) + + pipe = StableDiffusionControlNetPipeline( + vae=vae, + controlnet=controlnet, + unet=unet, + scheduler=scheduler, + tokenizer=tokenizer, + text_encoder=text_encoder, + feature_extractor=None, + safety_checker=None, + requires_safety_checker=False, + ) + + return pipe + +def save_videos_grid(videos: torch.Tensor, path: str, rescale=False, n_rows=6, fps=8): + if len(videos.shape) == 4: + videos = videos.unsqueeze(0) + videos = rearrange(videos, "b c t h w -> t b c h w") + outputs = [] + for x in videos: + x = torchvision.utils.make_grid(x, nrow=n_rows) + x = x.transpose(0, 1).transpose(1, 2).squeeze(-1) + if rescale: + x = (x + 1.0) / 2.0 # -1,1 -> 0,1 + x = torch.clamp((x * 255), 0, 255).numpy().astype(np.uint8) + outputs.append(x) + os.makedirs(os.path.dirname(path), exist_ok=True) + imageio.mimsave(path, outputs, fps=fps) + +def save_videos_from_pil_list(videos: list, path: str, fps=7): + for i in range(len(videos)): + videos[i] = ImageOps.scale(videos[i], 255) + + imageio.mimwrite(path, videos, fps=fps) + + +def seed_everything(seed: int) -> None: + r"""Sets the seed for generating random numbers in :pytorch:`PyTorch`, + :obj:`numpy` and :python:`Python`. + + Args: + seed (int): The desired seed. + """ + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + +def get_video_frames(video: np.ndarray, num_frames: int=200): + video_length = video.shape[0] + video_idx = np.linspace(0, video_length-1, num_frames, dtype=int) + video = video[video_idx, ...] + return video + +def random_audio_video_clip(audio: np.ndarray, video: np.ndarray, fps:float, \ + sample_rate:int=16000, duration:int=5, num_frames: int=20): + """ + Random sample video clips with duration + """ + video_length = video.shape[0] + audio_length = audio.shape[-1] + av_duration = int(video_length / fps) + assert av_duration >= duration,\ + f"video duration {av_duration} is less than {duration}" + + # random sample start time + start_time = random.uniform(0, av_duration - duration) + end_time = start_time + duration + + start_idx, end_idx = start_time / av_duration, end_time / av_duration + + video_start_frame, video_end_frame\ + = video_length * start_idx, video_length * end_idx + audio_start_frame, audio_end_frame\ + = audio_length * start_idx, audio_length * end_idx + + # print(f"time_idx : {start_time}:{end_time}") + # print(f"video_idx: {video_start_frame}:{video_end_frame}") + # print(f"audio_idx: {audio_start_frame}:{audio_end_frame}") + + audio_idx = np.linspace(audio_start_frame, audio_end_frame, sample_rate * duration, dtype=int) + video_idx = np.linspace(video_start_frame, video_end_frame, num_frames, dtype=int) + + audio = audio[..., audio_idx] + video = video[video_idx, ...] + + return audio, video + +def get_full_indices(reader: Union[decord.VideoReader, decord.AudioReader])\ + -> np.ndarray: + if isinstance(reader, decord.VideoReader): + return np.linspace(0, len(reader) - 1, len(reader), dtype=int) + elif isinstance(reader, decord.AudioReader): + return np.linspace(0, reader.shape[-1] - 1, reader.shape[-1], dtype=int) + +def get_frames(video_path:str, onset_list, frame_nums=1024): + video = decord.VideoReader(video_path) + video_frame = len(video) + + frames_list = [] + for start, end in onset_list: + video_start = int(start / frame_nums * video_frame) + video_end = int(end / frame_nums * video_frame) + + frames_list.extend(range(video_start, video_end)) + frames = video.get_batch(frames_list).asnumpy() + return frames + +def get_frames_in_video(video_path:str, onset_list, frame_nums=1024, audio_length_in_s=10): + # this function consider the video length + video = decord.VideoReader(video_path) + video_frame = len(video) + duration = video_frame / video.get_avg_fps() + frames_list = [] + video_onset_list = [] + for start, end in onset_list: + if int(start / frame_nums * duration) >= audio_length_in_s: + continue + video_start = int(start / audio_length_in_s * duration / frame_nums * video_frame) + if video_start >= video_frame: + continue + video_end = int(end / audio_length_in_s * duration / frame_nums * video_frame) + video_onset_list.append([int(start / audio_length_in_s * duration), int(end / audio_length_in_s * duration)]) + frames_list.extend(range(video_start, video_end)) + frames = video.get_batch(frames_list).asnumpy() + return frames, video_onset_list + +def save_multimodal(video, audio, output_path, audio_fps:int=16000, video_fps:int=8, remove_audio:bool=True): + imgs = [img for img in video] + # if audio.shape[0] == 1 or audio.shape[0] == 2: + # audio = audio.T #[len, channel] + # audio = np.repeat(audio, 2, axis=1) + output_dir = osp.dirname(output_path) + try: + wavfile.write(osp.join(output_dir, "audio.wav"), audio_fps, audio) + except: + sf.write(osp.join(output_dir, "audio.wav"), audio, audio_fps) + audio_clip = AudioFileClip(osp.join(output_dir, "audio.wav")) + # audio_clip = AudioArrayClip(audio, fps=audio_fps) + video_clip = ImageSequenceClip(imgs, fps=video_fps) + video_clip = video_clip.set_audio(audio_clip) + video_clip.write_videofile(output_path, video_fps, audio=True, audio_fps=audio_fps) + if remove_audio: + os.remove(osp.join(output_dir, "audio.wav")) + return + +def save_multimodal_by_frame(video, audio, output_path, audio_fps:int=16000): + imgs = [img for img in video] + # if audio.shape[0] == 1 or audio.shape[0] == 2: + # audio = audio.T #[len, channel] + # audio = np.repeat(audio, 2, axis=1) + # output_dir = osp.dirname(output_path) + output_dir = output_path + wavfile.write(osp.join(output_dir, "audio.wav"), audio_fps, audio) + audio_clip = AudioFileClip(osp.join(output_dir, "audio.wav")) + # audio_clip = AudioArrayClip(audio, fps=audio_fps) + os.makedirs(osp.join(output_dir, 'frames'), exist_ok=True) + for num, img in enumerate(imgs): + if isinstance(img, np.ndarray): + img = Image.fromarray(img.astype(np.uint8)) + img.save(osp.join(output_dir, 'frames', f"{num}.jpg")) + return + +def sanity_check(data: dict, save_path: str="sanity_check", batch_size: int=4, sample_rate: int=16000): + video_path = osp.join(save_path, 'video') + audio_path = osp.join(save_path, 'audio') + av_path = osp.join(save_path, 'av') + + video, audio, text = data['pixel_values'], data['audio'], data['text'] + video = (video / 2 + 0.5).clamp(0, 1) + + zero_rank_print(f"Saving {text} audio: {audio[0].shape} video: {video[0].shape}") + + for bsz in range(batch_size): + os.makedirs(video_path, exist_ok=True) + os.makedirs(audio_path, exist_ok=True) + os.makedirs(av_path, exist_ok=True) + # save_videos_grid(video[bsz:bsz+1,...], f"{osp.join(video_path, str(bsz) + '.mp4')}") + bsz_audio = audio[bsz,...].permute(1, 0).cpu().numpy() + bsz_video = video_tensor_to_np(video[bsz, ...]) + sf.write(f"{osp.join(audio_path, str(bsz) + '.wav')}", bsz_audio, sample_rate) + save_multimodal(bsz_video, bsz_audio, osp.join(av_path, str(bsz) + '.mp4')) + +def video_tensor_to_np(video: torch.Tensor, rescale: bool=True, scale: bool=False): + if scale: + video = (video / 2 + 0.5).clamp(0, 1) + # c f h w -> f h w c + if video.shape[0] == 3: + video = video.permute(1, 2, 3, 0).detach().cpu().numpy() + elif video.shape[1] == 3: + video = video.permute(0, 2, 3, 1).detach().cpu().numpy() + if rescale: + video = video * 255 + return video + +def composite_audio_video(video: str, audio: str, path:str, video_fps:int=7, audio_sample_rate:int=16000): + video = decord.VideoReader(video) + audio = decord.AudioReader(audio, sample_rate=audio_sample_rate) + audio = audio.get_batch(get_full_indices(audio)).asnumpy() + video = video.get_batch(get_full_indices(video)).asnumpy() + save_multimodal(video, audio, path, audio_fps=audio_sample_rate, video_fps=video_fps) + return + +# for video pipeline +def append_dims(x, target_dims): + """Appends dimensions to the end of a tensor until it has target_dims dimensions.""" + dims_to_append = target_dims - x.ndim + if dims_to_append < 0: + raise ValueError(f"input has {x.ndim} dims but target_dims is {target_dims}, which is less") + return x[(...,) + (None,) * dims_to_append] + +def resize_with_antialiasing(input, size, interpolation="bicubic", align_corners=True): + h, w = input.shape[-2:] + factors = (h / size[0], w / size[1]) + + # First, we have to determine sigma + # Taken from skimage: https://github.com/scikit-image/scikit-image/blob/v0.19.2/skimage/transform/_warps.py#L171 + sigmas = ( + max((factors[0] - 1.0) / 2.0, 0.001), + max((factors[1] - 1.0) / 2.0, 0.001), + ) + + # Now kernel size. Good results are for 3 sigma, but that is kind of slow. Pillow uses 1 sigma + # https://github.com/python-pillow/Pillow/blob/master/src/libImaging/Resample.c#L206 + # But they do it in the 2 passes, which gives better results. Let's try 2 sigmas for now + ks = int(max(2.0 * 2 * sigmas[0], 3)), int(max(2.0 * 2 * sigmas[1], 3)) + + # Make sure it is odd + if (ks[0] % 2) == 0: + ks = ks[0] + 1, ks[1] + + if (ks[1] % 2) == 0: + ks = ks[0], ks[1] + 1 + + input = _gaussian_blur2d(input, ks, sigmas) + + output = torch.nn.functional.interpolate(input, size=size, mode=interpolation, align_corners=align_corners) + return output + +def _gaussian_blur2d(input, kernel_size, sigma): + if isinstance(sigma, tuple): + sigma = torch.tensor([sigma], dtype=input.dtype) + else: + sigma = sigma.to(dtype=input.dtype) + + ky, kx = int(kernel_size[0]), int(kernel_size[1]) + bs = sigma.shape[0] + kernel_x = _gaussian(kx, sigma[:, 1].view(bs, 1)) + kernel_y = _gaussian(ky, sigma[:, 0].view(bs, 1)) + out_x = _filter2d(input, kernel_x[..., None, :]) + out = _filter2d(out_x, kernel_y[..., None]) + + return out + +def _filter2d(input, kernel): + # prepare kernel + b, c, h, w = input.shape + tmp_kernel = kernel[:, None, ...].to(device=input.device, dtype=input.dtype) + + tmp_kernel = tmp_kernel.expand(-1, c, -1, -1) + + height, width = tmp_kernel.shape[-2:] + + padding_shape: list[int] = _compute_padding([height, width]) + input = torch.nn.functional.pad(input, padding_shape, mode="reflect") + + # kernel and input tensor reshape to align element-wise or batch-wise params + tmp_kernel = tmp_kernel.reshape(-1, 1, height, width) + input = input.view(-1, tmp_kernel.size(0), input.size(-2), input.size(-1)) + + # convolve the tensor with the kernel. + output = torch.nn.functional.conv2d(input, tmp_kernel, groups=tmp_kernel.size(0), padding=0, stride=1) + + out = output.view(b, c, h, w) + return out + + +def _gaussian(window_size: int, sigma): + if isinstance(sigma, float): + sigma = torch.tensor([[sigma]]) + + batch_size = sigma.shape[0] + + x = (torch.arange(window_size, device=sigma.device, dtype=sigma.dtype) - window_size // 2).expand(batch_size, -1) + + if window_size % 2 == 0: + x = x + 0.5 + + gauss = torch.exp(-x.pow(2.0) / (2 * sigma.pow(2.0))) + + return gauss / gauss.sum(-1, keepdim=True) + +def _compute_padding(kernel_size): + """Compute padding tuple.""" + # 4 or 6 ints: (padding_left, padding_right,padding_top,padding_bottom) + # https://pytorch.org/docs/stable/nn.html#torch.nn.functional.pad + if len(kernel_size) < 2: + raise AssertionError(kernel_size) + computed = [k - 1 for k in kernel_size] + + # for even kernels we need to do asymmetric padding :( + out_padding = 2 * len(kernel_size) * [0] + + for i in range(len(kernel_size)): + computed_tmp = computed[-(i + 1)] + + pad_front = computed_tmp // 2 + pad_rear = computed_tmp - pad_front + + out_padding[2 * i + 0] = pad_front + out_padding[2 * i + 1] = pad_rear + + return out_padding + +def print_gpu_memory_usage(info: str, cuda_id:int=0): + + print(f">>> {info} <<<") + reserved = torch.cuda.memory_reserved(cuda_id) / 1024 ** 3 + used = torch.cuda.memory_allocated(cuda_id) / 1024 ** 3 + + print("total: ", reserved, "G") + print("used: ", used, "G") + print("available: ", reserved - used, "G") + +# use for dsp mel2spec +@dataclass(frozen=True) +class SpectrogramParams: + """ + Parameters for the conversion from audio to spectrograms to images and back. + + Includes helpers to convert to and from EXIF tags, allowing these parameters to be stored + within spectrogram images. + + To understand what these parameters do and to customize them, read `spectrogram_converter.py` + and the linked torchaudio documentation. + """ + + # Whether the audio is stereo or mono + stereo: bool = False + + # FFT parameters + sample_rate: int = 44100 + step_size_ms: int = 10 + window_duration_ms: int = 100 + padded_duration_ms: int = 400 + + # Mel scale parameters + num_frequencies: int = 200 + # TODO(hayk): Set these to [20, 20000] for newer models + min_frequency: int = 0 + max_frequency: int = 10000 + mel_scale_norm: T.Optional[str] = None + mel_scale_type: str = "htk" + max_mel_iters: int = 200 + + # Griffin Lim parameters + num_griffin_lim_iters: int = 32 + + # Image parameterization + power_for_image: float = 0.25 + + class ExifTags(Enum): + """ + Custom EXIF tags for the spectrogram image. + """ + + SAMPLE_RATE = 11000 + STEREO = 11005 + STEP_SIZE_MS = 11010 + WINDOW_DURATION_MS = 11020 + PADDED_DURATION_MS = 11030 + + NUM_FREQUENCIES = 11040 + MIN_FREQUENCY = 11050 + MAX_FREQUENCY = 11060 + + POWER_FOR_IMAGE = 11070 + MAX_VALUE = 11080 + + @property + def n_fft(self) -> int: + """ + The number of samples in each STFT window, with padding. + """ + return int(self.padded_duration_ms / 1000.0 * self.sample_rate) + + @property + def win_length(self) -> int: + """ + The number of samples in each STFT window. + """ + return int(self.window_duration_ms / 1000.0 * self.sample_rate) + + @property + def hop_length(self) -> int: + """ + The number of samples between each STFT window. + """ + return int(self.step_size_ms / 1000.0 * self.sample_rate) + + def to_exif(self) -> T.Dict[int, T.Any]: + """ + Return a dictionary of EXIF tags for the current values. + """ + return { + self.ExifTags.SAMPLE_RATE.value: self.sample_rate, + self.ExifTags.STEREO.value: self.stereo, + self.ExifTags.STEP_SIZE_MS.value: self.step_size_ms, + self.ExifTags.WINDOW_DURATION_MS.value: self.window_duration_ms, + self.ExifTags.PADDED_DURATION_MS.value: self.padded_duration_ms, + self.ExifTags.NUM_FREQUENCIES.value: self.num_frequencies, + self.ExifTags.MIN_FREQUENCY.value: self.min_frequency, + self.ExifTags.MAX_FREQUENCY.value: self.max_frequency, + self.ExifTags.POWER_FOR_IMAGE.value: float(self.power_for_image), + } + +class SpectrogramImageConverter: + """ + Convert between spectrogram images and audio segments. + + This is a wrapper around SpectrogramConverter that additionally converts from spectrograms + to images and back. The real audio processing lives in SpectrogramConverter. + """ + + def __init__(self, params: SpectrogramParams, device: str = "cuda"): + self.p = params + self.device = device + self.converter = SpectrogramConverter(params=params, device=device) + + def spectrogram_image_from_audio( + self, + segment: pydub.AudioSegment, + ) -> Image.Image: + """ + Compute a spectrogram image from an audio segment. + + Args: + segment: Audio segment to convert + + Returns: + Spectrogram image (in pillow format) + """ + assert int(segment.frame_rate) == self.p.sample_rate, "Sample rate mismatch" + + if self.p.stereo: + if segment.channels == 1: + print("WARNING: Mono audio but stereo=True, cloning channel") + segment = segment.set_channels(2) + elif segment.channels > 2: + print("WARNING: Multi channel audio, reducing to stereo") + segment = segment.set_channels(2) + else: + if segment.channels > 1: + print("WARNING: Stereo audio but stereo=False, setting to mono") + segment = segment.set_channels(1) + + spectrogram = self.converter.spectrogram_from_audio(segment) + + image = image_from_spectrogram( + spectrogram, + power=self.p.power_for_image, + ) + + # Store conversion params in exif metadata of the image + exif_data = self.p.to_exif() + exif_data[SpectrogramParams.ExifTags.MAX_VALUE.value] = float(np.max(spectrogram)) + exif = image.getexif() + exif.update(exif_data.items()) + + return image + + def audio_from_spectrogram_image( + self, + image: Image.Image, + apply_filters: bool = True, + max_value: float = 30e6, + ) -> pydub.AudioSegment: + """ + Reconstruct an audio segment from a spectrogram image. + + Args: + image: Spectrogram image (in pillow format) + apply_filters: Apply post-processing to improve the reconstructed audio + max_value: Scaled max amplitude of the spectrogram. Shouldn't matter. + """ + spectrogram = spectrogram_from_image( + image, + max_value=max_value, + power=self.p.power_for_image, + stereo=self.p.stereo, + ) + + segment = self.converter.audio_from_spectrogram( + spectrogram, + apply_filters=apply_filters, + ) + + return segment + +def image_from_spectrogram(spectrogram: np.ndarray, power: float = 0.25) -> Image.Image: + """ + Compute a spectrogram image from a spectrogram magnitude array. + + This is the inverse of spectrogram_from_image, except for discretization error from + quantizing to uint8. + + Args: + spectrogram: (channels, frequency, time) + power: A power curve to apply to the spectrogram to preserve contrast + + Returns: + image: (frequency, time, channels) + """ + # Rescale to 0-1 + max_value = np.max(spectrogram) + data = spectrogram / max_value + + # Apply the power curve + data = np.power(data, power) + + # Rescale to 0-255 + data = data * 255 + + # Invert + data = 255 - data + + # Convert to uint8 + data = data.astype(np.uint8) + + # Munge channels into a PIL image + if data.shape[0] == 1: + # TODO(hayk): Do we want to write single channel to disk instead? + image = Image.fromarray(data[0], mode="L").convert("RGB") + elif data.shape[0] == 2: + data = np.array([np.zeros_like(data[0]), data[0], data[1]]).transpose(1, 2, 0) + image = Image.fromarray(data, mode="RGB") + else: + raise NotImplementedError(f"Unsupported number of channels: {data.shape[0]}") + + # Flip Y + image = image.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + + return image + + +def spectrogram_from_image( + image: Image.Image, + power: float = 0.25, + stereo: bool = False, + max_value: float = 30e6, +) -> np.ndarray: + """ + Compute a spectrogram magnitude array from a spectrogram image. + + This is the inverse of image_from_spectrogram, except for discretization error from + quantizing to uint8. + + Args: + image: (frequency, time, channels) + power: The power curve applied to the spectrogram + stereo: Whether the spectrogram encodes stereo data + max_value: The max value of the original spectrogram. In practice doesn't matter. + + Returns: + spectrogram: (channels, frequency, time) + """ + # Convert to RGB if single channel + if image.mode in ("P", "L"): + image = image.convert("RGB") + + # Flip Y + image = image.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + + # Munge channels into a numpy array of (channels, frequency, time) + data = np.array(image).transpose(2, 0, 1) + if stereo: + # Take the G and B channels as done in image_from_spectrogram + data = data[[1, 2], :, :] + else: + data = data[0:1, :, :] + + # Convert to floats + data = data.astype(np.float32) + + # Invert + data = 255 - data + + # Rescale to 0-1 + data = data / 255 + + # Reverse the power curve + data = np.power(data, 1 / power) + + # Rescale to max value + data = data * max_value + + return data + +class SpectrogramConverter: + """ + Convert between audio segments and spectrogram tensors using torchaudio. + + In this class a "spectrogram" is defined as a (batch, time, frequency) tensor with float values + that represent the amplitude of the frequency at that time bucket (in the frequency domain). + Frequencies are given in the perceptul Mel scale defined by the params. A more specific term + used in some functions is "mel amplitudes". + + The spectrogram computed from `spectrogram_from_audio` is complex valued, but it only + returns the amplitude, because the phase is chaotic and hard to learn. The function + `audio_from_spectrogram` is an approximate inverse of `spectrogram_from_audio`, which + approximates the phase information using the Griffin-Lim algorithm. + + Each channel in the audio is treated independently, and the spectrogram has a batch dimension + equal to the number of channels in the input audio segment. + + Both the Griffin Lim algorithm and the Mel scaling process are lossy. + + For more information, see https://pytorch.org/audio/stable/transforms.html + """ + + def __init__(self, params: SpectrogramParams, device: str = "cuda"): + self.p = params + + self.device = check_device(device) + + if device.lower().startswith("mps"): + warnings.warn( + "WARNING: MPS does not support audio operations, falling back to CPU for them", + stacklevel=2, + ) + self.device = "cpu" + + # https://pytorch.org/audio/stable/generated/torchaudio.transforms.Spectrogram.html + self.spectrogram_func = torchaudio.transforms.Spectrogram( + n_fft=params.n_fft, + hop_length=params.hop_length, + win_length=params.win_length, + pad=0, + window_fn=torch.hann_window, + power=None, + normalized=False, + wkwargs=None, + center=True, + pad_mode="reflect", + onesided=True, + ).to(self.device) + + # https://pytorch.org/audio/stable/generated/torchaudio.transforms.GriffinLim.html + self.inverse_spectrogram_func = torchaudio.transforms.GriffinLim( + n_fft=params.n_fft, + n_iter=params.num_griffin_lim_iters, + win_length=params.win_length, + hop_length=params.hop_length, + window_fn=torch.hann_window, + power=1.0, + wkwargs=None, + momentum=0.99, + length=None, + rand_init=True, + ).to(self.device) + + # https://pytorch.org/audio/stable/generated/torchaudio.transforms.MelScale.html + self.mel_scaler = torchaudio.transforms.MelScale( + n_mels=params.num_frequencies, + sample_rate=params.sample_rate, + f_min=params.min_frequency, + f_max=params.max_frequency, + n_stft=params.n_fft // 2 + 1, + norm=params.mel_scale_norm, + mel_scale=params.mel_scale_type, + ).to(self.device) + + # https://pytorch.org/audio/stable/generated/torchaudio.transforms.InverseMelScale.html + self.inverse_mel_scaler = torchaudio.transforms.InverseMelScale( + n_stft=params.n_fft // 2 + 1, + n_mels=params.num_frequencies, + sample_rate=params.sample_rate, + f_min=params.min_frequency, + f_max=params.max_frequency, + # max_iter=params.max_mel_iters, # for higher verson of torchaudio + # tolerance_loss=1e-5, # for higher verson of torchaudio + # tolerance_change=1e-8, # for higher verson of torchaudio + # sgdargs=None, # for higher verson of torchaudio + norm=params.mel_scale_norm, + mel_scale=params.mel_scale_type, + ).to(self.device) + + def spectrogram_from_audio( + self, + audio: pydub.AudioSegment, + ) -> np.ndarray: + """ + Compute a spectrogram from an audio segment. + + Args: + audio: Audio segment which must match the sample rate of the params + + Returns: + spectrogram: (channel, frequency, time) + """ + assert int(audio.frame_rate) == self.p.sample_rate, "Audio sample rate must match params" + + # Get the samples as a numpy array in (batch, samples) shape + waveform = np.array([c.get_array_of_samples() for c in audio.split_to_mono()]) + + # Convert to floats if necessary + if waveform.dtype != np.float32: + waveform = waveform.astype(np.float32) + + waveform_tensor = torch.from_numpy(waveform).to(self.device) + amplitudes_mel = self.mel_amplitudes_from_waveform(waveform_tensor) + return amplitudes_mel.cpu().numpy() + + def audio_from_spectrogram( + self, + spectrogram: np.ndarray, + apply_filters: bool = True, + ) -> pydub.AudioSegment: + """ + Reconstruct an audio segment from a spectrogram. + + Args: + spectrogram: (batch, frequency, time) + apply_filters: Post-process with normalization and compression + + Returns: + audio: Audio segment with channels equal to the batch dimension + """ + # Move to device + amplitudes_mel = torch.from_numpy(spectrogram).to(self.device) + + # Reconstruct the waveform + waveform = self.waveform_from_mel_amplitudes(amplitudes_mel) + + # Convert to audio segment + segment = audio_from_waveform( + samples=waveform.cpu().numpy(), + sample_rate=self.p.sample_rate, + # Normalize the waveform to the range [-1, 1] + normalize=True, + ) + + # Optionally apply post-processing filters + if apply_filters: + segment = apply_filters_func( + segment, + compression=False, + ) + + return segment + + def mel_amplitudes_from_waveform( + self, + waveform: torch.Tensor, + ) -> torch.Tensor: + """ + Torch-only function to compute Mel-scale amplitudes from a waveform. + + Args: + waveform: (batch, samples) + + Returns: + amplitudes_mel: (batch, frequency, time) + """ + # Compute the complex-valued spectrogram + spectrogram_complex = self.spectrogram_func(waveform) + + # Take the magnitude + amplitudes = torch.abs(spectrogram_complex) + + # Convert to mel scale + return self.mel_scaler(amplitudes) + + def waveform_from_mel_amplitudes( + self, + amplitudes_mel: torch.Tensor, + ) -> torch.Tensor: + """ + Torch-only function to approximately reconstruct a waveform from Mel-scale amplitudes. + + Args: + amplitudes_mel: (batch, frequency, time) + + Returns: + waveform: (batch, samples) + """ + # Convert from mel scale to linear + amplitudes_linear = self.inverse_mel_scaler(amplitudes_mel) + + # Run the approximate algorithm to compute the phase and recover the waveform + return self.inverse_spectrogram_func(amplitudes_linear) + +def check_device(device: str, backup: str = "cpu") -> str: + """ + Check that the device is valid and available. If not, + """ + cuda_not_found = device.lower().startswith("cuda") and not torch.cuda.is_available() + mps_not_found = device.lower().startswith("mps") and not torch.backends.mps.is_available() + + if cuda_not_found or mps_not_found: + warnings.warn(f"WARNING: {device} is not available, using {backup} instead.", stacklevel=3) + return backup + + return device + +def audio_from_waveform( + samples: np.ndarray, sample_rate: int, normalize: bool = False +) -> pydub.AudioSegment: + """ + Convert a numpy array of samples of a waveform to an audio segment. + + Args: + samples: (channels, samples) array + """ + # Normalize volume to fit in int16 + if normalize: + samples *= np.iinfo(np.int16).max / np.max(np.abs(samples)) + + # Transpose and convert to int16 + samples = samples.transpose(1, 0) + samples = samples.astype(np.int16) + + # Write to the bytes of a WAV file + wav_bytes = io.BytesIO() + wavfile.write(wav_bytes, sample_rate, samples) + wav_bytes.seek(0) + + # Read into pydub + return pydub.AudioSegment.from_wav(wav_bytes) + + +def apply_filters_func(segment: pydub.AudioSegment, compression: bool = False) -> pydub.AudioSegment: + """ + Apply post-processing filters to the audio segment to compress it and + keep at a -10 dBFS level. + """ + # TODO(hayk): Come up with a principled strategy for these filters and experiment end-to-end. + # TODO(hayk): Is this going to make audio unbalanced between sequential clips? + + if compression: + segment = pydub.effects.normalize( + segment, + headroom=0.1, + ) + + segment = segment.apply_gain(-10 - segment.dBFS) + + # TODO(hayk): This is quite slow, ~1.7 seconds on a beefy CPU + segment = pydub.effects.compress_dynamic_range( + segment, + threshold=-20.0, + ratio=4.0, + attack=5.0, + release=50.0, + ) + + desired_db = -12 + segment = segment.apply_gain(desired_db - segment.dBFS) + + segment = pydub.effects.normalize( + segment, + headroom=0.1, + ) + + return segment + +def shave_segments(path, n_shave_prefix_segments=1): + """ + Removes segments. Positive values shave the first segments, negative shave the last segments. + """ + if n_shave_prefix_segments >= 0: + return ".".join(path.split(".")[n_shave_prefix_segments:]) + else: + return ".".join(path.split(".")[:n_shave_prefix_segments]) + + +def renew_resnet_paths(old_list, n_shave_prefix_segments=0): + """ + Updates paths inside resnets to the new naming scheme (local renaming) + """ + mapping = [] + for old_item in old_list: + new_item = old_item.replace("in_layers.0", "norm1") + new_item = new_item.replace("in_layers.2", "conv1") + + new_item = new_item.replace("out_layers.0", "norm2") + new_item = new_item.replace("out_layers.3", "conv2") + + new_item = new_item.replace("emb_layers.1", "time_emb_proj") + new_item = new_item.replace("skip_connection", "conv_shortcut") + + new_item = shave_segments(new_item, n_shave_prefix_segments=n_shave_prefix_segments) + + mapping.append({"old": old_item, "new": new_item}) + + return mapping + + +def renew_vae_resnet_paths(old_list, n_shave_prefix_segments=0): + """ + Updates paths inside resnets to the new naming scheme (local renaming) + """ + mapping = [] + for old_item in old_list: + new_item = old_item + + new_item = new_item.replace("nin_shortcut", "conv_shortcut") + new_item = shave_segments(new_item, n_shave_prefix_segments=n_shave_prefix_segments) + + mapping.append({"old": old_item, "new": new_item}) + + return mapping + + +def renew_attention_paths(old_list, n_shave_prefix_segments=0): + """ + Updates paths inside attentions to the new naming scheme (local renaming) + """ + mapping = [] + for old_item in old_list: + new_item = old_item + + # new_item = new_item.replace('norm.weight', 'group_norm.weight') + # new_item = new_item.replace('norm.bias', 'group_norm.bias') + + # new_item = new_item.replace('proj_out.weight', 'proj_attn.weight') + # new_item = new_item.replace('proj_out.bias', 'proj_attn.bias') + + # new_item = shave_segments(new_item, n_shave_prefix_segments=n_shave_prefix_segments) + + mapping.append({"old": old_item, "new": new_item}) + + return mapping + + +def renew_vae_attention_paths(old_list, n_shave_prefix_segments=0): + """ + Updates paths inside attentions to the new naming scheme (local renaming) + """ + mapping = [] + for old_item in old_list: + new_item = old_item + + new_item = new_item.replace("norm.weight", "group_norm.weight") + new_item = new_item.replace("norm.bias", "group_norm.bias") + + new_item = new_item.replace("q.weight", "to_q.weight") + new_item = new_item.replace("q.bias", "to_q.bias") + + new_item = new_item.replace("k.weight", "to_k.weight") + new_item = new_item.replace("k.bias", "to_k.bias") + + new_item = new_item.replace("v.weight", "to_v.weight") + new_item = new_item.replace("v.bias", "to_v.bias") + + new_item = new_item.replace("proj_out.weight", "to_out.0.weight") + new_item = new_item.replace("proj_out.bias", "to_out.0.bias") + + new_item = shave_segments(new_item, n_shave_prefix_segments=n_shave_prefix_segments) + + mapping.append({"old": old_item, "new": new_item}) + return mapping + + +def assign_to_checkpoint( + paths, checkpoint, old_checkpoint, attention_paths_to_split=None, additional_replacements=None, config=None +): + """ + This does the final conversion step: take locally converted weights and apply a global renaming to them. It splits + attention layers, and takes into account additional replacements that may arise. + + Assigns the weights to the new checkpoint. + """ + assert isinstance(paths, list), "Paths should be a list of dicts containing 'old' and 'new' keys." + + # Splits the attention layers into three variables. + if attention_paths_to_split is not None: + for path, path_map in attention_paths_to_split.items(): + old_tensor = old_checkpoint[path] + channels = old_tensor.shape[0] // 3 + + target_shape = (-1, channels) if len(old_tensor.shape) == 3 else (-1) + + num_heads = old_tensor.shape[0] // config["num_head_channels"] // 3 + + old_tensor = old_tensor.reshape((num_heads, 3 * channels // num_heads) + old_tensor.shape[1:]) + query, key, value = old_tensor.split(channels // num_heads, dim=1) + + checkpoint[path_map["query"]] = query.reshape(target_shape) + checkpoint[path_map["key"]] = key.reshape(target_shape) + checkpoint[path_map["value"]] = value.reshape(target_shape) + + for path in paths: + new_path = path["new"] + + # These have already been assigned + if attention_paths_to_split is not None and new_path in attention_paths_to_split: + continue + + # Global renaming happens here + new_path = new_path.replace("middle_block.0", "mid_block.resnets.0") + new_path = new_path.replace("middle_block.1", "mid_block.attentions.0") + new_path = new_path.replace("middle_block.2", "mid_block.resnets.1") + + if additional_replacements is not None: + for replacement in additional_replacements: + new_path = new_path.replace(replacement["old"], replacement["new"]) + + # proj_attn.weight has to be converted from conv 1D to linear + if "proj_attn.weight" in new_path: + checkpoint[new_path] = old_checkpoint[path["old"]][:, :, 0] + elif 'to_out.0.weight' in new_path: + checkpoint[new_path] = old_checkpoint[path['old']].squeeze() + elif any([qkv in new_path for qkv in ['to_q', 'to_k', 'to_v']]): + checkpoint[new_path] = old_checkpoint[path['old']].squeeze() + else: + checkpoint[new_path] = old_checkpoint[path["old"]] + + +def conv_attn_to_linear(checkpoint): + keys = list(checkpoint.keys()) + attn_keys = ["query.weight", "key.weight", "value.weight"] + for key in keys: + if ".".join(key.split(".")[-2:]) in attn_keys: + if checkpoint[key].ndim > 2: + checkpoint[key] = checkpoint[key][:, :, 0, 0] + elif "proj_attn.weight" in key: + if checkpoint[key].ndim > 2: + checkpoint[key] = checkpoint[key][:, :, 0] + + +def create_unet_diffusers_config(original_config, image_size: int, controlnet=False): + """ + Creates a config for the diffusers based on the config of the LDM model. + """ + if controlnet: + unet_params = original_config.model.params.control_stage_config.params + else: + unet_params = original_config.model.params.unet_config.params + + vae_params = original_config.model.params.first_stage_config.params.ddconfig + + block_out_channels = [unet_params.model_channels * mult for mult in unet_params.channel_mult] + + down_block_types = [] + resolution = 1 + for i in range(len(block_out_channels)): + block_type = "CrossAttnDownBlock2D" if resolution in unet_params.attention_resolutions else "DownBlock2D" + down_block_types.append(block_type) + if i != len(block_out_channels) - 1: + resolution *= 2 + + up_block_types = [] + for i in range(len(block_out_channels)): + block_type = "CrossAttnUpBlock2D" if resolution in unet_params.attention_resolutions else "UpBlock2D" + up_block_types.append(block_type) + resolution //= 2 + + vae_scale_factor = 2 ** (len(vae_params.ch_mult) - 1) + + head_dim = unet_params.num_heads if "num_heads" in unet_params else None + use_linear_projection = ( + unet_params.use_linear_in_transformer if "use_linear_in_transformer" in unet_params else False + ) + if use_linear_projection: + # stable diffusion 2-base-512 and 2-768 + if head_dim is None: + head_dim = [5, 10, 20, 20] + + class_embed_type = None + projection_class_embeddings_input_dim = None + + if "num_classes" in unet_params: + if unet_params.num_classes == "sequential": + class_embed_type = "projection" + assert "adm_in_channels" in unet_params + projection_class_embeddings_input_dim = unet_params.adm_in_channels + else: + raise NotImplementedError(f"Unknown conditional unet num_classes config: {unet_params.num_classes}") + + config = { + "sample_size": image_size // vae_scale_factor, + "in_channels": unet_params.in_channels, + "down_block_types": tuple(down_block_types), + "block_out_channels": tuple(block_out_channels), + "layers_per_block": unet_params.num_res_blocks, + "cross_attention_dim": unet_params.context_dim, + "attention_head_dim": head_dim, + "use_linear_projection": use_linear_projection, + "class_embed_type": class_embed_type, + "projection_class_embeddings_input_dim": projection_class_embeddings_input_dim, + } + + if not controlnet: + config["out_channels"] = unet_params.out_channels + config["up_block_types"] = tuple(up_block_types) + + return config + + +def create_vae_diffusers_config(original_config, image_size: int): + """ + Creates a config for the diffusers based on the config of the LDM model. + """ + vae_params = original_config.model.params.first_stage_config.params.ddconfig + _ = original_config.model.params.first_stage_config.params.embed_dim + + block_out_channels = [vae_params.ch * mult for mult in vae_params.ch_mult] + down_block_types = ["DownEncoderBlock2D"] * len(block_out_channels) + up_block_types = ["UpDecoderBlock2D"] * len(block_out_channels) + + config = { + "sample_size": image_size, + "in_channels": vae_params.in_channels, + "out_channels": vae_params.out_ch, + "down_block_types": tuple(down_block_types), + "up_block_types": tuple(up_block_types), + "block_out_channels": tuple(block_out_channels), + "latent_channels": vae_params.z_channels, + "layers_per_block": vae_params.num_res_blocks, + } + return config + + +def create_diffusers_schedular(original_config): + schedular = DDIMScheduler( + num_train_timesteps=original_config.model.params.timesteps, + beta_start=original_config.model.params.linear_start, + beta_end=original_config.model.params.linear_end, + beta_schedule="scaled_linear", + ) + return schedular + +def convert_ldm_unet_checkpoint(checkpoint, config, path=None, extract_ema=False, controlnet=False): + """ + Takes a state dict and a config, and returns a converted checkpoint. + """ + + # extract state_dict for UNet + unet_state_dict = {} + keys = list(checkpoint.keys()) + + if controlnet: + unet_key = "control_model." + else: + unet_key = "model.diffusion_model." + + # at least a 100 parameters have to start with `model_ema` in order for the checkpoint to be EMA + if sum(k.startswith("model_ema") for k in keys) > 100 and extract_ema: + print(f"Checkpoint {path} has both EMA and non-EMA weights.") + print( + "In this conversion only the EMA weights are extracted. If you want to instead extract the non-EMA" + " weights (useful to continue fine-tuning), please make sure to remove the `--extract_ema` flag." + ) + for key in keys: + if key.startswith("model.diffusion_model"): + flat_ema_key = "model_ema." + "".join(key.split(".")[1:]) + unet_state_dict[key.replace(unet_key, "")] = checkpoint.pop(flat_ema_key) + else: + if sum(k.startswith("model_ema") for k in keys) > 100: + print( + "In this conversion only the non-EMA weights are extracted. If you want to instead extract the EMA" + " weights (usually better for inference), please make sure to add the `--extract_ema` flag." + ) + + for key in keys: + if key.startswith(unet_key): + unet_state_dict[key.replace(unet_key, "")] = checkpoint.pop(key) + + new_checkpoint = {} + + new_checkpoint["time_embedding.linear_1.weight"] = unet_state_dict["time_embed.0.weight"] + new_checkpoint["time_embedding.linear_1.bias"] = unet_state_dict["time_embed.0.bias"] + new_checkpoint["time_embedding.linear_2.weight"] = unet_state_dict["time_embed.2.weight"] + new_checkpoint["time_embedding.linear_2.bias"] = unet_state_dict["time_embed.2.bias"] + + if config["class_embed_type"] is None: + # No parameters to port + ... + elif config["class_embed_type"] == "timestep" or config["class_embed_type"] == "projection": + new_checkpoint["class_embedding.linear_1.weight"] = unet_state_dict["label_emb.0.0.weight"] + new_checkpoint["class_embedding.linear_1.bias"] = unet_state_dict["label_emb.0.0.bias"] + new_checkpoint["class_embedding.linear_2.weight"] = unet_state_dict["label_emb.0.2.weight"] + new_checkpoint["class_embedding.linear_2.bias"] = unet_state_dict["label_emb.0.2.bias"] + else: + raise NotImplementedError(f"Not implemented `class_embed_type`: {config['class_embed_type']}") + + new_checkpoint["conv_in.weight"] = unet_state_dict["input_blocks.0.0.weight"] + new_checkpoint["conv_in.bias"] = unet_state_dict["input_blocks.0.0.bias"] + + if not controlnet: + new_checkpoint["conv_norm_out.weight"] = unet_state_dict["out.0.weight"] + new_checkpoint["conv_norm_out.bias"] = unet_state_dict["out.0.bias"] + new_checkpoint["conv_out.weight"] = unet_state_dict["out.2.weight"] + new_checkpoint["conv_out.bias"] = unet_state_dict["out.2.bias"] + + # Retrieves the keys for the input blocks only + num_input_blocks = len({".".join(layer.split(".")[:2]) for layer in unet_state_dict if "input_blocks" in layer}) + input_blocks = { + layer_id: [key for key in unet_state_dict if f"input_blocks.{layer_id}" in key] + for layer_id in range(num_input_blocks) + } + + # Retrieves the keys for the middle blocks only + num_middle_blocks = len({".".join(layer.split(".")[:2]) for layer in unet_state_dict if "middle_block" in layer}) + middle_blocks = { + layer_id: [key for key in unet_state_dict if f"middle_block.{layer_id}" in key] + for layer_id in range(num_middle_blocks) + } + + # Retrieves the keys for the output blocks only + num_output_blocks = len({".".join(layer.split(".")[:2]) for layer in unet_state_dict if "output_blocks" in layer}) + output_blocks = { + layer_id: [key for key in unet_state_dict if f"output_blocks.{layer_id}" in key] + for layer_id in range(num_output_blocks) + } + + for i in range(1, num_input_blocks): + block_id = (i - 1) // (config["layers_per_block"] + 1) + layer_in_block_id = (i - 1) % (config["layers_per_block"] + 1) + + resnets = [ + key for key in input_blocks[i] if f"input_blocks.{i}.0" in key and f"input_blocks.{i}.0.op" not in key + ] + attentions = [key for key in input_blocks[i] if f"input_blocks.{i}.1" in key] + + if f"input_blocks.{i}.0.op.weight" in unet_state_dict: + new_checkpoint[f"down_blocks.{block_id}.downsamplers.0.conv.weight"] = unet_state_dict.pop( + f"input_blocks.{i}.0.op.weight" + ) + new_checkpoint[f"down_blocks.{block_id}.downsamplers.0.conv.bias"] = unet_state_dict.pop( + f"input_blocks.{i}.0.op.bias" + ) + + paths = renew_resnet_paths(resnets) + meta_path = {"old": f"input_blocks.{i}.0", "new": f"down_blocks.{block_id}.resnets.{layer_in_block_id}"} + assign_to_checkpoint( + paths, new_checkpoint, unet_state_dict, additional_replacements=[meta_path], config=config + ) + + if len(attentions): + paths = renew_attention_paths(attentions) + meta_path = {"old": f"input_blocks.{i}.1", "new": f"down_blocks.{block_id}.attentions.{layer_in_block_id}"} + assign_to_checkpoint( + paths, new_checkpoint, unet_state_dict, additional_replacements=[meta_path], config=config + ) + + resnet_0 = middle_blocks[0] + attentions = middle_blocks[1] + resnet_1 = middle_blocks[2] + + resnet_0_paths = renew_resnet_paths(resnet_0) + assign_to_checkpoint(resnet_0_paths, new_checkpoint, unet_state_dict, config=config) + + resnet_1_paths = renew_resnet_paths(resnet_1) + assign_to_checkpoint(resnet_1_paths, new_checkpoint, unet_state_dict, config=config) + + attentions_paths = renew_attention_paths(attentions) + meta_path = {"old": "middle_block.1", "new": "mid_block.attentions.0"} + assign_to_checkpoint( + attentions_paths, new_checkpoint, unet_state_dict, additional_replacements=[meta_path], config=config + ) + + for i in range(num_output_blocks): + block_id = i // (config["layers_per_block"] + 1) + layer_in_block_id = i % (config["layers_per_block"] + 1) + output_block_layers = [shave_segments(name, 2) for name in output_blocks[i]] + output_block_list = {} + + for layer in output_block_layers: + layer_id, layer_name = layer.split(".")[0], shave_segments(layer, 1) + if layer_id in output_block_list: + output_block_list[layer_id].append(layer_name) + else: + output_block_list[layer_id] = [layer_name] + + if len(output_block_list) > 1: + resnets = [key for key in output_blocks[i] if f"output_blocks.{i}.0" in key] + attentions = [key for key in output_blocks[i] if f"output_blocks.{i}.1" in key] + + resnet_0_paths = renew_resnet_paths(resnets) + paths = renew_resnet_paths(resnets) + + meta_path = {"old": f"output_blocks.{i}.0", "new": f"up_blocks.{block_id}.resnets.{layer_in_block_id}"} + assign_to_checkpoint( + paths, new_checkpoint, unet_state_dict, additional_replacements=[meta_path], config=config + ) + + output_block_list = {k: sorted(v) for k, v in output_block_list.items()} + if ["conv.bias", "conv.weight"] in output_block_list.values(): + index = list(output_block_list.values()).index(["conv.bias", "conv.weight"]) + new_checkpoint[f"up_blocks.{block_id}.upsamplers.0.conv.weight"] = unet_state_dict[ + f"output_blocks.{i}.{index}.conv.weight" + ] + new_checkpoint[f"up_blocks.{block_id}.upsamplers.0.conv.bias"] = unet_state_dict[ + f"output_blocks.{i}.{index}.conv.bias" + ] + + # Clear attentions as they have been attributed above. + if len(attentions) == 2: + attentions = [] + + if len(attentions): + paths = renew_attention_paths(attentions) + meta_path = { + "old": f"output_blocks.{i}.1", + "new": f"up_blocks.{block_id}.attentions.{layer_in_block_id}", + } + assign_to_checkpoint( + paths, new_checkpoint, unet_state_dict, additional_replacements=[meta_path], config=config + ) + else: + resnet_0_paths = renew_resnet_paths(output_block_layers, n_shave_prefix_segments=1) + for path in resnet_0_paths: + old_path = ".".join(["output_blocks", str(i), path["old"]]) + new_path = ".".join(["up_blocks", str(block_id), "resnets", str(layer_in_block_id), path["new"]]) + + new_checkpoint[new_path] = unet_state_dict[old_path] + + if controlnet: + # conditioning embedding + + orig_index = 0 + + new_checkpoint["controlnet_cond_embedding.conv_in.weight"] = unet_state_dict.pop( + f"input_hint_block.{orig_index}.weight" + ) + new_checkpoint["controlnet_cond_embedding.conv_in.bias"] = unet_state_dict.pop( + f"input_hint_block.{orig_index}.bias" + ) + + orig_index += 2 + + diffusers_index = 0 + + while diffusers_index < 6: + new_checkpoint[f"controlnet_cond_embedding.blocks.{diffusers_index}.weight"] = unet_state_dict.pop( + f"input_hint_block.{orig_index}.weight" + ) + new_checkpoint[f"controlnet_cond_embedding.blocks.{diffusers_index}.bias"] = unet_state_dict.pop( + f"input_hint_block.{orig_index}.bias" + ) + diffusers_index += 1 + orig_index += 2 + + new_checkpoint["controlnet_cond_embedding.conv_out.weight"] = unet_state_dict.pop( + f"input_hint_block.{orig_index}.weight" + ) + new_checkpoint["controlnet_cond_embedding.conv_out.bias"] = unet_state_dict.pop( + f"input_hint_block.{orig_index}.bias" + ) + + # down blocks + for i in range(num_input_blocks): + new_checkpoint[f"controlnet_down_blocks.{i}.weight"] = unet_state_dict.pop(f"zero_convs.{i}.0.weight") + new_checkpoint[f"controlnet_down_blocks.{i}.bias"] = unet_state_dict.pop(f"zero_convs.{i}.0.bias") + + # mid block + new_checkpoint["controlnet_mid_block.weight"] = unet_state_dict.pop("middle_block_out.0.weight") + new_checkpoint["controlnet_mid_block.bias"] = unet_state_dict.pop("middle_block_out.0.bias") + + return new_checkpoint + + +def convert_ldm_vae_checkpoint(checkpoint, config, only_decoder=False, only_encoder=False): + # extract state dict for VAE + vae_state_dict = {} + vae_key = "first_stage_model." + keys = list(checkpoint.keys()) + for key in keys: + if key.startswith(vae_key): + vae_state_dict[key.replace(vae_key, "")] = checkpoint.get(key) + + new_checkpoint = {} + + new_checkpoint["encoder.conv_in.weight"] = vae_state_dict["encoder.conv_in.weight"] + new_checkpoint["encoder.conv_in.bias"] = vae_state_dict["encoder.conv_in.bias"] + new_checkpoint["encoder.conv_out.weight"] = vae_state_dict["encoder.conv_out.weight"] + new_checkpoint["encoder.conv_out.bias"] = vae_state_dict["encoder.conv_out.bias"] + new_checkpoint["encoder.conv_norm_out.weight"] = vae_state_dict["encoder.norm_out.weight"] + new_checkpoint["encoder.conv_norm_out.bias"] = vae_state_dict["encoder.norm_out.bias"] + + new_checkpoint["decoder.conv_in.weight"] = vae_state_dict["decoder.conv_in.weight"] + new_checkpoint["decoder.conv_in.bias"] = vae_state_dict["decoder.conv_in.bias"] + new_checkpoint["decoder.conv_out.weight"] = vae_state_dict["decoder.conv_out.weight"] + new_checkpoint["decoder.conv_out.bias"] = vae_state_dict["decoder.conv_out.bias"] + new_checkpoint["decoder.conv_norm_out.weight"] = vae_state_dict["decoder.norm_out.weight"] + new_checkpoint["decoder.conv_norm_out.bias"] = vae_state_dict["decoder.norm_out.bias"] + + new_checkpoint["quant_conv.weight"] = vae_state_dict["quant_conv.weight"] + new_checkpoint["quant_conv.bias"] = vae_state_dict["quant_conv.bias"] + new_checkpoint["post_quant_conv.weight"] = vae_state_dict["post_quant_conv.weight"] + new_checkpoint["post_quant_conv.bias"] = vae_state_dict["post_quant_conv.bias"] + + # Retrieves the keys for the encoder down blocks only + num_down_blocks = len({".".join(layer.split(".")[:3]) for layer in vae_state_dict if "encoder.down" in layer}) + down_blocks = { + layer_id: [key for key in vae_state_dict if f"down.{layer_id}" in key] for layer_id in range(num_down_blocks) + } + + # Retrieves the keys for the decoder up blocks only + num_up_blocks = len({".".join(layer.split(".")[:3]) for layer in vae_state_dict if "decoder.up" in layer}) + up_blocks = { + layer_id: [key for key in vae_state_dict if f"up.{layer_id}" in key] for layer_id in range(num_up_blocks) + } + + for i in range(num_down_blocks): + resnets = [key for key in down_blocks[i] if f"down.{i}" in key and f"down.{i}.downsample" not in key] + + if f"encoder.down.{i}.downsample.conv.weight" in vae_state_dict: + new_checkpoint[f"encoder.down_blocks.{i}.downsamplers.0.conv.weight"] = vae_state_dict.pop( + f"encoder.down.{i}.downsample.conv.weight" + ) + new_checkpoint[f"encoder.down_blocks.{i}.downsamplers.0.conv.bias"] = vae_state_dict.pop( + f"encoder.down.{i}.downsample.conv.bias" + ) + + paths = renew_vae_resnet_paths(resnets) + meta_path = {"old": f"down.{i}.block", "new": f"down_blocks.{i}.resnets"} + assign_to_checkpoint(paths, new_checkpoint, vae_state_dict, additional_replacements=[meta_path], config=config) + + mid_resnets = [key for key in vae_state_dict if "encoder.mid.block" in key] + num_mid_res_blocks = 2 + for i in range(1, num_mid_res_blocks + 1): + resnets = [key for key in mid_resnets if f"encoder.mid.block_{i}" in key] + + paths = renew_vae_resnet_paths(resnets) + meta_path = {"old": f"mid.block_{i}", "new": f"mid_block.resnets.{i - 1}"} + assign_to_checkpoint(paths, new_checkpoint, vae_state_dict, additional_replacements=[meta_path], config=config) + + mid_attentions = [key for key in vae_state_dict if "encoder.mid.attn" in key] + paths = renew_vae_attention_paths(mid_attentions) + meta_path = {"old": "mid.attn_1", "new": "mid_block.attentions.0"} + assign_to_checkpoint(paths, new_checkpoint, vae_state_dict, additional_replacements=[meta_path], config=config) + conv_attn_to_linear(new_checkpoint) + + for i in range(num_up_blocks): + block_id = num_up_blocks - 1 - i + resnets = [ + key for key in up_blocks[block_id] if f"up.{block_id}" in key and f"up.{block_id}.upsample" not in key + ] + + if f"decoder.up.{block_id}.upsample.conv.weight" in vae_state_dict: + new_checkpoint[f"decoder.up_blocks.{i}.upsamplers.0.conv.weight"] = vae_state_dict[ + f"decoder.up.{block_id}.upsample.conv.weight" + ] + new_checkpoint[f"decoder.up_blocks.{i}.upsamplers.0.conv.bias"] = vae_state_dict[ + f"decoder.up.{block_id}.upsample.conv.bias" + ] + + paths = renew_vae_resnet_paths(resnets) + meta_path = {"old": f"up.{block_id}.block", "new": f"up_blocks.{i}.resnets"} + assign_to_checkpoint(paths, new_checkpoint, vae_state_dict, additional_replacements=[meta_path], config=config) + + mid_resnets = [key for key in vae_state_dict if "decoder.mid.block" in key] + num_mid_res_blocks = 2 + for i in range(1, num_mid_res_blocks + 1): + resnets = [key for key in mid_resnets if f"decoder.mid.block_{i}" in key] + + paths = renew_vae_resnet_paths(resnets) + meta_path = {"old": f"mid.block_{i}", "new": f"mid_block.resnets.{i - 1}"} + assign_to_checkpoint(paths, new_checkpoint, vae_state_dict, additional_replacements=[meta_path], config=config) + + mid_attentions = [key for key in vae_state_dict if "decoder.mid.attn" in key] + paths = renew_vae_attention_paths(mid_attentions) + meta_path = {"old": "mid.attn_1", "new": "mid_block.attentions.0"} + assign_to_checkpoint(paths, new_checkpoint, vae_state_dict, additional_replacements=[meta_path], config=config) + conv_attn_to_linear(new_checkpoint) + + if only_decoder: + new_checkpoint = {k: v for k, v in new_checkpoint.items() if k.startswith('decoder') or k.startswith('post_quant')} + elif only_encoder: + new_checkpoint = {k: v for k, v in new_checkpoint.items() if k.startswith('encoder') or k.startswith('quant')} + + return new_checkpoint + +def convert_ldm_clip_checkpoint(checkpoint): + keys = list(checkpoint.keys()) + + text_model_dict = {} + for key in keys: + if key.startswith("cond_stage_model.transformer"): + text_model_dict[key[len("cond_stage_model.transformer.") :]] = checkpoint[key] + + return text_model_dict + +def convert_lora_model_level(state_dict, unet, text_encoder=None, LORA_PREFIX_UNET="lora_unet", LORA_PREFIX_TEXT_ENCODER="lora_te", alpha=0.6): + """convert lora in model level instead of pipeline leval + """ + + visited = [] + + # directly update weight in diffusers model + for key in state_dict: + # it is suggested to print out the key, it usually will be something like below + # "lora_te_text_model_encoder_layers_0_self_attn_k_proj.lora_down.weight" + + # as we have set the alpha beforehand, so just skip + if ".alpha" in key or key in visited: + continue + + if "text" in key: + layer_infos = key.split(".")[0].split(LORA_PREFIX_TEXT_ENCODER + "_")[-1].split("_") + assert text_encoder is not None, ( + 'text_encoder must be passed since lora contains text encoder layers') + curr_layer = text_encoder + else: + layer_infos = key.split(".")[0].split(LORA_PREFIX_UNET + "_")[-1].split("_") + curr_layer = unet + + # find the target layer + temp_name = layer_infos.pop(0) + while len(layer_infos) > -1: + try: + curr_layer = curr_layer.__getattr__(temp_name) + if len(layer_infos) > 0: + temp_name = layer_infos.pop(0) + elif len(layer_infos) == 0: + break + except Exception: + if len(temp_name) > 0: + temp_name += "_" + layer_infos.pop(0) + else: + temp_name = layer_infos.pop(0) + + pair_keys = [] + if "lora_down" in key: + pair_keys.append(key.replace("lora_down", "lora_up")) + pair_keys.append(key) + else: + pair_keys.append(key) + pair_keys.append(key.replace("lora_up", "lora_down")) + + # update weight + # NOTE: load lycon, meybe have bugs :( + if 'conv_in' in pair_keys[0]: + weight_up = state_dict[pair_keys[0]].to(torch.float32) + weight_down = state_dict[pair_keys[1]].to(torch.float32) + weight_up = weight_up.view(weight_up.size(0), -1) + weight_down = weight_down.view(weight_down.size(0), -1) + shape = [e for e in curr_layer.weight.data.shape] + shape[1] = 4 + curr_layer.weight.data[:, :4, ...] += alpha * (weight_up @ weight_down).view(*shape) + elif 'conv' in pair_keys[0]: + weight_up = state_dict[pair_keys[0]].to(torch.float32) + weight_down = state_dict[pair_keys[1]].to(torch.float32) + weight_up = weight_up.view(weight_up.size(0), -1) + weight_down = weight_down.view(weight_down.size(0), -1) + shape = [e for e in curr_layer.weight.data.shape] + curr_layer.weight.data += alpha * (weight_up @ weight_down).view(*shape) + elif len(state_dict[pair_keys[0]].shape) == 4: + weight_up = state_dict[pair_keys[0]].squeeze(3).squeeze(2).to(torch.float32) + weight_down = state_dict[pair_keys[1]].squeeze(3).squeeze(2).to(torch.float32) + curr_layer.weight.data += alpha * torch.mm(weight_up, weight_down).unsqueeze(2).unsqueeze(3).to(curr_layer.weight.data.device) + else: + weight_up = state_dict[pair_keys[0]].to(torch.float32) + weight_down = state_dict[pair_keys[1]].to(torch.float32) + curr_layer.weight.data += alpha * torch.mm(weight_up, weight_down).to(curr_layer.weight.data.device) + + # update visited list + for item in pair_keys: + visited.append(item) + + return unet, text_encoder + +def denormalize_spectrogram( + data: torch.Tensor, + max_value: float = 200, + min_value: float = 1e-5, + power: float = 1, + inverse: bool = False, +) -> torch.Tensor: + + max_value = np.log(max_value) + min_value = np.log(min_value) + + # Flip Y axis: image origin at the top-left corner, spectrogram origin at the bottom-left corner + data = torch.flip(data, [1]) + + assert len(data.shape) == 3, "Expected 3 dimensions, got {}".format(len(data.shape)) + + if data.shape[0] == 1: + data = data.repeat(3, 1, 1) + + assert data.shape[0] == 3, "Expected 3 channels, got {}".format(data.shape[0]) + data = data[0] + + # Reverse the power curve + data = torch.pow(data, 1 / power) + + # Invert + if inverse: + data = 1 - data + + # Rescale to max value + spectrogram = data * (max_value - min_value) + min_value + + return spectrogram + +class ToTensor1D(torchvision.transforms.ToTensor): + + def __call__(self, tensor: np.ndarray): + tensor_2d = super(ToTensor1D, self).__call__(tensor[..., np.newaxis]) + + return tensor_2d.squeeze_(0) + +def scale(old_value, old_min, old_max, new_min, new_max): + old_range = (old_max - old_min) + new_range = (new_max - new_min) + new_value = (((old_value - old_min) * new_range) / old_range) + new_min + + return new_value + +def read_frames_with_moviepy(video_path, max_frame_nums=None): + clip = VideoFileClip(video_path) + duration = clip.duration + frames = [] + for frame in clip.iter_frames(): + frames.append(frame) + if max_frame_nums is not None: + frames_idx = np.linspace(0, len(frames) - 1, max_frame_nums, dtype=int) + return np.array(frames)[frames_idx,...], duration + +def read_frames_with_moviepy_resample(video_path, save_path): + vision_transform_list = [ + transforms.Resize((128, 128)), + transforms.CenterCrop((112, 112)), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + ] + video_transform = transforms.Compose(vision_transform_list) + os.makedirs(save_path, exist_ok=True) + command = f'ffmpeg -v quiet -y -i \"{video_path}\" -f image2 -vf \"scale=-1:360,fps=15\" -qscale:v 3 \"{save_path}\"/frame%06d.jpg' + os.system(command) + frame_list = glob.glob(f'{save_path}/*.jpg') + frame_list.sort() + convert_tensor = transforms.ToTensor() + frame_list = [convert_tensor(np.array(Image.open(frame))) for frame in frame_list] + imgs = torch.stack(frame_list, dim=0) + imgs = video_transform(imgs) + imgs = imgs.permute(1, 0, 2, 3) + return imgs \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..80258daac773e340a742f01ce92f402f57194cbb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,21 @@ +decord==0.6.0 +diffusers==0.20.0 +einops==0.7.0 +imageio==2.27.0 +ipdb==0.13.13 +librosa==0.9.2 +moviepy==1.0.3 +numpy==1.23.5 +omegaconf==2.3.0 +opencv_python==4.8.0.76 +Pillow==10.2.0 +pydub==0.25.1 +safetensors==0.3.3 +scipy==1.12.0 +soundfile==0.12.1 +torch==2.1.2 +torchaudio==2.1.2 +torchvision==0.16.2 +tqdm==4.65.0 +transformers==4.32.1 +xformers==0.0.23.post1