This commit is contained in:
sayakpaul
2024-11-26 12:28:44 +05:30
parent 2dbddd566e
commit 58a06320ba
3 changed files with 88 additions and 59 deletions
+13 -10
View File
@@ -3,11 +3,11 @@ from typing import Any, Dict, Tuple
import numpy as np import numpy as np
import torch import torch
from torchvision import transforms import torch.nn as nn
from accelerate.logging import get_logger from accelerate.logging import get_logger
from torchvision import transforms
from torchvision.transforms import InterpolationMode from torchvision.transforms import InterpolationMode
from torchvision.transforms.functional import resize from torchvision.transforms.functional import resize
import torch.nn as nn
# Must import after torch because this can sometimes lead to a nasty segmentation fault, or stack smashing error # Must import after torch because this can sometimes lead to a nasty segmentation fault, or stack smashing error
@@ -16,14 +16,17 @@ import decord # isort:skip
decord.bridge.set_bridge("torch") decord.bridge.set_bridge("torch")
import sys import sys
sys.path.append("..") sys.path.append("..")
from dataset import VideoDataset as VDS from dataset import VideoDataset as VDS
logger = get_logger(__name__) logger = get_logger(__name__)
# TODO (sayakpaul): probably not all buckets are needed for Mochi-1? # TODO (sayakpaul): probably not all buckets are needed for Mochi-1?
HEIGHT_BUCKETS = [256, 320, 384, 480, 512, 576, 720, 768, 960, 1024, 1280, 1536] HEIGHT_BUCKETS = [256, 320, 384, 480, 512, 576, 720, 768, 960, 1024, 1280, 1536]
WIDTH_BUCKETS = [256, 320, 384, 480, 512, 576, 720, 768, 848, 960, 1024, 1280, 1536] WIDTH_BUCKETS = [256, 320, 384, 480, 512, 576, 720, 768, 848, 960, 1024, 1280, 1536]
FRAME_BUCKETS = [16, 24, 32, 48, 64, 80, 85] FRAME_BUCKETS = [16, 24, 32, 48, 64, 80, 85]
@@ -68,7 +71,7 @@ class VideoDataset(VDS):
# This is hardcoded for now. # This is hardcoded for now.
# Output of the VAE encoding is 2 * output_channels and then it's # Output of the VAE encoding is 2 * output_channels and then it's
# temporal compression factor is 6. Initially, the VAE encodings will have # temporal compression factor is 6. Initially, the VAE encodings will have
# 24 latent number of frames. So, if we were to train with a # 24 latent number of frames. So, if we were to train with a
# max frame size of 85 and frame bucket of [85], we need to have the following logic. # max frame size of 85 and frame bucket of [85], we need to have the following logic.
latent_num_frames = video_latents.size(0) latent_num_frames = video_latents.size(0)
num_frames = ((latent_num_frames // 2) * (VAE_TEMPORAL_SCALE_FACTOR + 1) + 1) num_frames = ((latent_num_frames // 2) * (VAE_TEMPORAL_SCALE_FACTOR + 1) + 1)
@@ -169,13 +172,13 @@ class VideoDatasetWithFlexibleResize(VideoDataset):
) )
frame_indices = list( frame_indices = list(
range( range(
0, 0,
video_num_frames, video_num_frames,
1 if video_num_frames < nearest_frame_bucket else video_num_frames // nearest_frame_bucket 1 if video_num_frames < nearest_frame_bucket else video_num_frames // nearest_frame_bucket
) )
) )
frames = video_reader.get_batch(frame_indices) frames = video_reader.get_batch(frame_indices)
# Pad or truncate frames to match the bucket size # Pad or truncate frames to match the bucket size
if video_num_frames < nearest_frame_bucket: if video_num_frames < nearest_frame_bucket:
pad_size = nearest_frame_bucket - video_num_frames pad_size = nearest_frame_bucket - video_num_frames
@@ -210,7 +213,7 @@ class VideoDatasetWithFlexibleResize(VideoDataset):
def _resize_for_rectangle_crop(self, arr: torch.Tensor, image_size: Tuple[int, int]) -> torch.Tensor: def _resize_for_rectangle_crop(self, arr: torch.Tensor, image_size: Tuple[int, int]) -> torch.Tensor:
""" """
Resize frames for rectangular cropping. Resize frames for rectangular cropping.
Args: Args:
arr (torch.Tensor): The video frames tensor [N, C, H, W]. arr (torch.Tensor): The video frames tensor [N, C, H, W].
image_size (Tuple[int, int]): The target resolution (height, width). image_size (Tuple[int, int]): The target resolution (height, width).
@@ -239,4 +242,4 @@ class VideoDatasetWithFlexibleResize(VideoDataset):
else: else:
raise NotImplementedError(f"Unsupported reshape mode: {self.video_reshape_mode}") raise NotImplementedError(f"Unsupported reshape mode: {self.video_reshape_mode}")
return transforms.functional.crop(arr, top=top, left=left, height=image_size[0], width=image_size[1]) return transforms.functional.crop(arr, top=top, left=left, height=image_size[0], width=image_size[1])
+6 -4
View File
@@ -8,12 +8,13 @@ import pathlib
import queue import queue
import traceback import traceback
import uuid import uuid
from contextlib import nullcontext
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from contextlib import nullcontext
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
import torch import torch
import torch.distributed as dist import torch.distributed as dist
from dataset_mochi import VideoDatasetWithFlexibleResize
from diffusers import AutoencoderKLMochi from diffusers import AutoencoderKLMochi
from diffusers.training_utils import set_seed from diffusers.training_utils import set_seed
from diffusers.utils import export_to_video, get_logger from diffusers.utils import export_to_video, get_logger
@@ -21,7 +22,6 @@ from torch.utils.data import DataLoader
from torchvision import transforms from torchvision import transforms
from tqdm import tqdm from tqdm import tqdm
from transformers import T5EncoderModel, T5Tokenizer from transformers import T5EncoderModel, T5Tokenizer
from dataset_mochi import VideoDatasetWithFlexibleResize
import decord # isort:skip import decord # isort:skip
@@ -29,6 +29,8 @@ import decord # isort:skip
decord.bridge.set_bridge("torch") decord.bridge.set_bridge("torch")
import sys import sys
sys.path.append("..") sys.path.append("..")
from dataset import BucketSampler from dataset import BucketSampler
@@ -489,11 +491,11 @@ def main():
text_encoder = T5EncoderModel.from_pretrained( text_encoder = T5EncoderModel.from_pretrained(
args.model_id, subfolder="text_encoder", torch_dtype=weight_dtype args.model_id, subfolder="text_encoder", torch_dtype=weight_dtype
).to(device) ).to(device)
vae = AutoencoderKLMochi.from_pretrained( vae = AutoencoderKLMochi.from_pretrained(
args.model_id, subfolder="vae", torch_dtype=weight_dtype args.model_id, subfolder="vae", torch_dtype=weight_dtype
).to(device) ).to(device)
if args.use_slicing: if args.use_slicing:
vae.enable_slicing() vae.enable_slicing()
if args.use_tiling: if args.use_tiling:
+69 -45
View File
@@ -13,7 +13,9 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import copy
import gc import gc
import json
import logging import logging
import math import math
import os import os
@@ -26,7 +28,6 @@ import diffusers
import torch import torch
import transformers import transformers
import wandb import wandb
import copy
from accelerate import Accelerator, DistributedType from accelerate import Accelerator, DistributedType
from accelerate.logging import get_logger from accelerate.logging import get_logger
from accelerate.utils import ( from accelerate.utils import (
@@ -43,7 +44,11 @@ from diffusers import (
) )
from diffusers.models.autoencoders.vae import DiagonalGaussianDistribution from diffusers.models.autoencoders.vae import DiagonalGaussianDistribution
from diffusers.optimization import get_scheduler from diffusers.optimization import get_scheduler
from diffusers.training_utils import cast_training_params, compute_density_for_timestep_sampling, compute_loss_weighting_for_sd3 from diffusers.training_utils import (
cast_training_params,
compute_density_for_timestep_sampling,
compute_loss_weighting_for_sd3,
)
from diffusers.utils import convert_unet_state_dict_to_peft, export_to_video from diffusers.utils import convert_unet_state_dict_to_peft, export_to_video
from diffusers.utils.hub_utils import load_or_create_model_card, populate_model_card from diffusers.utils.hub_utils import load_or_create_model_card, populate_model_card
from diffusers.utils.torch_utils import is_compiled_module from diffusers.utils.torch_utils import is_compiled_module
@@ -53,10 +58,13 @@ from torch.utils.data import DataLoader
from tqdm.auto import tqdm from tqdm.auto import tqdm
from transformers import AutoTokenizer, T5EncoderModel from transformers import AutoTokenizer, T5EncoderModel
from args import get_args # isort:skip from args import get_args # isort:skip
from dataset_mochi import VideoDatasetWithFlexibleResize # isort:skip from dataset_mochi import VideoDatasetWithFlexibleResize # isort:skip
import sys import sys
sys.path.append("..") sys.path.append("..")
from dataset import BucketSampler # isort:skip from dataset import BucketSampler # isort:skip
@@ -78,7 +86,7 @@ def save_model_card(
widget_dict = [] widget_dict = []
if videos is not None and len(videos) > 0: if videos is not None and len(videos) > 0:
for i, video in enumerate(videos): for i, video in enumerate(videos):
export_to_video(video, os.path.join(repo_folder, f"final_video_{i}.mp4", fps=fps)) export_to_video(video, os.path.join(repo_folder, f"final_video_{i}.mp4"), fps=fps)
widget_dict.append( widget_dict.append(
{ {
"text": validation_prompt if validation_prompt else " ", "text": validation_prompt if validation_prompt else " ",
@@ -147,7 +155,8 @@ def log_validation(
f"Running validation... \n Generating {args.num_validation_videos} videos with prompt: {pipeline_args['prompt']}." f"Running validation... \n Generating {args.num_validation_videos} videos with prompt: {pipeline_args['prompt']}."
) )
pipe = pipe.to(accelerator.device) if not args.enable_model_cpu_offload:
pipe = pipe.to(accelerator.device)
# run inference # run inference
generator = torch.Generator(device=accelerator.device).manual_seed(args.seed) if args.seed else None generator = torch.Generator(device=accelerator.device).manual_seed(args.seed) if args.seed else None
@@ -261,7 +270,7 @@ def main(args):
set_seed(args.seed) set_seed(args.seed)
# Handle the repository creation # Handle the repository creation
if accelerator.is_main_process: if accelerator.distributed_type == DistributedType.DEEPSPEED or accelerator.is_main_process:
if args.output_dir is not None: if args.output_dir is not None:
os.makedirs(args.output_dir, exist_ok=True) os.makedirs(args.output_dir, exist_ok=True)
@@ -283,7 +292,7 @@ def main(args):
subfolder="text_encoder", subfolder="text_encoder",
revision=args.revision, revision=args.revision,
) )
vae = AutoencoderKLMochi.from_pretrained( vae = AutoencoderKLMochi.from_pretrained(
args.pretrained_model_name_or_path, args.pretrained_model_name_or_path,
subfolder="vae", subfolder="vae",
@@ -295,10 +304,11 @@ def main(args):
if args.enable_tiling: if args.enable_tiling:
vae.enable_tiling() vae.enable_tiling()
# keep things in FP32.
text_encoder.requires_grad_(False) text_encoder.requires_grad_(False)
text_encoder.to(accelerator.device, dtype=weight_dtype) text_encoder.to(accelerator.device, dtype=torch.float32)
vae.requires_grad_(False) vae.requires_grad_(False)
vae.to(accelerator.device, dtype=weight_dtype) vae.to(accelerator.device, dtype=torch.float32)
load_dtype = torch.bfloat16 if "5b" in args.pretrained_model_name_or_path.lower() else torch.float16 load_dtype = torch.bfloat16 if "5b" in args.pretrained_model_name_or_path.lower() else torch.float16
transformer = MochiTransformer3DModel.from_pretrained( transformer = MochiTransformer3DModel.from_pretrained(
@@ -315,7 +325,7 @@ def main(args):
vae_in_channels = vae_config["latent_channels"] vae_in_channels = vae_config["latent_channels"]
has_latents_mean = "latents_mean" in vae_config and vae_config["latents_mean"] is not None has_latents_mean = "latents_mean" in vae_config and vae_config["latents_mean"] is not None
has_latents_std = "latents_std" in vae_config and vae_config["latents_std"] is not None has_latents_std = "latents_std" in vae_config and vae_config["latents_std"] is not None
VAE_SCALING_FACTOR = vae_config["scaling_factor"] VAE_SCALING_FACTOR = vae_config["scaling_factor"]
# For mixed precision training we cast all non-trainable weights (vae, text_encoder and transformer) to half-precision # For mixed precision training we cast all non-trainable weights (vae, text_encoder and transformer) to half-precision
@@ -344,10 +354,10 @@ def main(args):
raise ValueError( raise ValueError(
"Mixed precision training with bfloat16 is not supported on MPS. Please use fp16 (recommended) or fp32 instead." "Mixed precision training with bfloat16 is not supported on MPS. Please use fp16 (recommended) or fp32 instead."
) )
# keep the transformer in FP32.
transformer.requires_grad_(False) transformer.requires_grad_(False)
# transformer.to(accelerator.device, dtype=weight_dtype) transformer.to(accelerator.device, torch.float32)
transformer.to(accelerator.device)
if args.gradient_checkpointing: if args.gradient_checkpointing:
transformer.enable_gradient_checkpointing() transformer.enable_gradient_checkpointing()
@@ -355,7 +365,7 @@ def main(args):
transformer_lora_config = LoraConfig( transformer_lora_config = LoraConfig(
r=args.rank, r=args.rank,
lora_alpha=args.lora_alpha, lora_alpha=args.lora_alpha,
init_lora_weights=True, init_lora_weights="gaussian",
target_modules=args.target_modules, target_modules=args.target_modules,
) )
transformer.add_adapter(transformer_lora_config) transformer.add_adapter(transformer_lora_config)
@@ -482,6 +492,18 @@ def main(args):
accelerator.print(f"Using {optimizer.__class__.__name__} optimizer.") accelerator.print(f"Using {optimizer.__class__.__name__} optimizer.")
# Dataset and DataLoader # Dataset and DataLoader
if args.load_tensors and args.id_token:
with open(os.path.join(args.data_root, "data.jsonl")) as f:
contents = [json.loads(jline) for jline in f.read().splitlines()]
parsed_id_token = None
for content in contents:
if "id_token" in content:
parsed_id_token = content["id_token"]
if parsed_id_token is not None and parsed_id_token.strip() != args.id_token.strip():
raise ValueError(
f"Parsed `id_token` from serialized metadata is {parsed_id_token} and provided `id_token` is {args.id_token}. They should match."
)
dataset_init_kwargs = { dataset_init_kwargs = {
"data_root": args.data_root, "data_root": args.data_root,
"video_reshape_mode": args.video_reshape_mode, "video_reshape_mode": args.video_reshape_mode,
@@ -497,7 +519,8 @@ def main(args):
"random_flip": args.random_flip, "random_flip": args.random_flip,
} }
train_dataset = VideoDatasetWithFlexibleResize(**dataset_init_kwargs) train_dataset = VideoDatasetWithFlexibleResize(**dataset_init_kwargs)
collate_fn = CollateFunction(weight_dtype, args.load_tensors) # keeping things in FP32 for now.
collate_fn = CollateFunction(weight_dtype=torch.float32, load_tensors=args.load_tensors)
train_dataloader = DataLoader( train_dataloader = DataLoader(
train_dataset, train_dataset,
batch_size=1, batch_size=1,
@@ -619,7 +642,6 @@ def main(args):
if args.load_tensors: if args.load_tensors:
gc.collect() gc.collect()
torch.cuda.empty_cache() torch.cuda.empty_cache()
torch.cuda.synchronize(accelerator.device)
def get_sigmas(timesteps, n_dim=4, dtype=torch.float32): def get_sigmas(timesteps, n_dim=4, dtype=torch.float32):
sigmas = noise_scheduler_copy.sigmas.to(device=accelerator.device, dtype=dtype) sigmas = noise_scheduler_copy.sigmas.to(device=accelerator.device, dtype=dtype)
@@ -650,11 +672,11 @@ def main(args):
# Encode videos # Encode videos
if not args.load_tensors: if not args.load_tensors:
videos = videos.permute(0, 2, 1, 3, 4) # [B, C, F, H, W] videos = videos.permute(0, 2, 1, 3, 4) # [B, C, F, H, W]
latent_dist = vae.encode(videos).latent_dist latent_dist = vae.encode(videos.to(vae.dtype)).latent_dist
else: else:
latent_dist = DiagonalGaussianDistribution(videos) latent_dist = DiagonalGaussianDistribution(videos)
videos = latent_dist.sample() videos = latent_dist.sample()
if has_latents_mean and has_latents_std: if has_latents_mean and has_latents_std:
latents_mean = ( latents_mean = (
torch.tensor(vae_config["latents_mean"]).view(1, vae_in_channels, 1, 1, 1).to(videos.device, videos.dtype) torch.tensor(vae_config["latents_mean"]).view(1, vae_in_channels, 1, 1, 1).to(videos.device, videos.dtype)
@@ -665,8 +687,9 @@ def main(args):
videos = (videos - latents_mean) * VAE_SCALING_FACTOR / latents_std videos = (videos - latents_mean) * VAE_SCALING_FACTOR / latents_std
else: else:
videos = videos * VAE_SCALING_FACTOR videos = videos * VAE_SCALING_FACTOR
videos = videos.to(memory_format=torch.contiguous_format, dtype=weight_dtype) # keep in FP32 for now.
videos = videos.to(memory_format=torch.contiguous_format, dtype=torch.float32)
model_input = videos model_input = videos
# Encode prompts # Encode prompts
@@ -677,11 +700,11 @@ def main(args):
prompts, prompts,
model_config.max_text_seq_length, model_config.max_text_seq_length,
accelerator.device, accelerator.device,
weight_dtype, weight_dtype=weight_dtype,
requires_grad=False, requires_grad=False,
) )
else: else:
prompt_embeds = prompts.to(dtype=weight_dtype) prompt_embeds = prompts.to(weight_dtype)
prompt_attention_mask = prompt_attention_mask.to(accelerator.device) prompt_attention_mask = prompt_attention_mask.to(accelerator.device)
# Sample noise that will be added to the latents # Sample noise that will be added to the latents
@@ -697,15 +720,25 @@ def main(args):
logit_std=args.logit_std, logit_std=args.logit_std,
mode_scale=args.mode_scale, mode_scale=args.mode_scale,
) )
indices = (u * noise_scheduler_copy.config.num_train_timesteps).long() # indices = (u * noise_scheduler_copy.config.num_train_timesteps).long()
timesteps = noise_scheduler_copy.timesteps[indices].to(device=model_input.device) # timesteps = noise_scheduler_copy.timesteps[indices].to(device=model_input.device)
# revisit.
timesteps = (u * noise_scheduler_copy.config.num_train_timesteps)
# Add noise according to flow matching. # Add noise according to flow matching.
# zt = (1 - texp) * x + texp * z1 # zt = (1 - texp) * x + texp * z1
sigmas = get_sigmas(timesteps, n_dim=model_input.ndim, dtype=model_input.dtype) sigmas = get_sigmas(
noisy_model_input = (1.0 - sigmas) * model_input + sigmas * noise timesteps=noise_scheduler_copy.timesteps[timesteps.long()].to(device=model_input.device),
n_dim=model_input.ndim,
dtype=model_input.dtype
)
noisy_model_input = (1.0 - sigmas) * model_input + sigmas * noise # do we need to revisit this?
noisy_model_input = noisy_model_input.to(weight_dtype)
# Predict the noise residual # Predict the noise residual
actual_num_train_timesteps = float(noise_scheduler_copy.config.num_train_timesteps)
timesteps = (1 - (timesteps / actual_num_train_timesteps)) * actual_num_train_timesteps # revisit
timesteps = timesteps.to(device=model_input.device)
model_pred = transformer( model_pred = transformer(
hidden_states=noisy_model_input, hidden_states=noisy_model_input,
encoder_hidden_states=prompt_embeds, encoder_hidden_states=prompt_embeds,
@@ -719,7 +752,8 @@ def main(args):
weighting = compute_loss_weighting_for_sd3(weighting_scheme=args.weighting_scheme, sigmas=sigmas) weighting = compute_loss_weighting_for_sd3(weighting_scheme=args.weighting_scheme, sigmas=sigmas)
# flow matching loss # flow matching loss
target = noise - model_input # target = noise - model_input
target = model_input - noise # as discussed with Ajay
loss = torch.mean( loss = torch.mean(
(weighting * (model_pred.float() - target.float()) ** 2).reshape(batch_size, -1), (weighting * (model_pred.float() - target.float()) ** 2).reshape(batch_size, -1),
@@ -787,11 +821,13 @@ def main(args):
if global_step >= args.max_train_steps: if global_step >= args.max_train_steps:
break break
if accelerator.is_main_process: if global_step >= args.max_train_steps:
break
if accelerator.distributed_type == DistributedType.DEEPSPEED or accelerator.is_main_process:
if args.validation_prompt is not None and (epoch + 1) % args.validation_epochs == 0: if args.validation_prompt is not None and (epoch + 1) % args.validation_epochs == 0:
accelerator.print("===== Memory before validation =====") accelerator.print("===== Memory before validation =====")
print_memory(accelerator.device) print_memory(accelerator.device)
torch.cuda.synchronize(accelerator.device)
pipe = MochiPipeline.from_pretrained( pipe = MochiPipeline.from_pretrained(
args.pretrained_model_name_or_path, args.pretrained_model_name_or_path,
@@ -799,7 +835,6 @@ def main(args):
scheduler=scheduler, scheduler=scheduler,
revision=args.revision, revision=args.revision,
variant=args.variant, variant=args.variant,
# torch_dtype=weight_dtype,
) )
if args.enable_slicing: if args.enable_slicing:
@@ -831,25 +866,18 @@ def main(args):
print_memory(accelerator.device) print_memory(accelerator.device)
reset_memory(accelerator.device) reset_memory(accelerator.device)
del pipe.text_encoder
del pipe.vae
del pipe del pipe
gc.collect() gc.collect()
torch.cuda.empty_cache() torch.cuda.empty_cache()
torch.cuda.synchronize(accelerator.device)
accelerator.wait_for_everyone() accelerator.wait_for_everyone()
if accelerator.is_main_process: if accelerator.distributed_type == DistributedType.DEEPSPEED or accelerator.is_main_process:
transformer = unwrap_model(transformer) transformer = unwrap_model(transformer)
dtype = (
torch.float16
if args.mixed_precision == "fp16"
else torch.bfloat16
if args.mixed_precision == "bf16"
else torch.float32
)
# transformer = transformer.to(dtype)
transformer_lora_layers = get_peft_model_state_dict(transformer) transformer_lora_layers = get_peft_model_state_dict(transformer)
MochiPipeline.save_lora_weights( MochiPipeline.save_lora_weights(
save_directory=args.output_dir, save_directory=args.output_dir,
transformer_lora_layers=transformer_lora_layers, transformer_lora_layers=transformer_lora_layers,
@@ -863,7 +891,6 @@ def main(args):
gc.collect() gc.collect()
torch.cuda.empty_cache() torch.cuda.empty_cache()
torch.cuda.synchronize(accelerator.device)
# Final test inference # Final test inference
validation_outputs = [] validation_outputs = []
@@ -871,14 +898,12 @@ def main(args):
accelerator.print("===== Memory before testing =====") accelerator.print("===== Memory before testing =====")
print_memory(accelerator.device) print_memory(accelerator.device)
reset_memory(accelerator.device) reset_memory(accelerator.device)
pipe = MochiPipeline.from_pretrained( pipe = MochiPipeline.from_pretrained(
args.pretrained_model_name_or_path, args.pretrained_model_name_or_path,
revision=args.revision, revision=args.revision,
variant=args.variant, variant=args.variant,
# torch_dtype=weight_dtype,
) )
pipe.scheduler = FlowMatchEulerDiscreteScheduler.from_config(pipe.scheduler.config)
if args.enable_slicing: if args.enable_slicing:
pipe.vae.enable_slicing() pipe.vae.enable_slicing()
@@ -898,7 +923,6 @@ def main(args):
pipeline_args = { pipeline_args = {
"prompt": validation_prompt, "prompt": validation_prompt,
"guidance_scale": 4.5, "guidance_scale": 4.5,
"use_dynamic_cfg": args.use_dynamic_cfg,
"height": args.height, "height": args.height,
"width": args.width, "width": args.width,
} }