Upload utility source
Browse files- utils/dataset.py +220 -0
- utils/distributed.py +125 -0
- utils/lmdb.py +72 -0
- utils/loss.py +81 -0
- utils/misc.py +39 -0
- utils/scheduler.py +194 -0
- utils/wan_wrapper.py +319 -0
utils/dataset.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from utils.lmdb import get_array_shape_from_lmdb, retrieve_row_from_lmdb
|
| 2 |
+
from torch.utils.data import Dataset
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
import lmdb
|
| 6 |
+
import json
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from PIL import Image
|
| 9 |
+
import os
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class TextDataset(Dataset):
|
| 13 |
+
def __init__(self, prompt_path, extended_prompt_path=None):
|
| 14 |
+
with open(prompt_path, encoding="utf-8") as f:
|
| 15 |
+
self.prompt_list = [line.rstrip() for line in f]
|
| 16 |
+
|
| 17 |
+
if extended_prompt_path is not None:
|
| 18 |
+
with open(extended_prompt_path, encoding="utf-8") as f:
|
| 19 |
+
self.extended_prompt_list = [line.rstrip() for line in f]
|
| 20 |
+
assert len(self.extended_prompt_list) == len(self.prompt_list)
|
| 21 |
+
else:
|
| 22 |
+
self.extended_prompt_list = None
|
| 23 |
+
|
| 24 |
+
def __len__(self):
|
| 25 |
+
return len(self.prompt_list)
|
| 26 |
+
|
| 27 |
+
def __getitem__(self, idx):
|
| 28 |
+
batch = {
|
| 29 |
+
"prompts": self.prompt_list[idx],
|
| 30 |
+
"idx": idx,
|
| 31 |
+
}
|
| 32 |
+
if self.extended_prompt_list is not None:
|
| 33 |
+
batch["extended_prompts"] = self.extended_prompt_list[idx]
|
| 34 |
+
return batch
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class ODERegressionLMDBDataset(Dataset):
|
| 38 |
+
def __init__(self, data_path: str, max_pair: int = int(1e8)):
|
| 39 |
+
self.env = lmdb.open(data_path, readonly=True,
|
| 40 |
+
lock=False, readahead=False, meminit=False)
|
| 41 |
+
|
| 42 |
+
self.latents_shape = get_array_shape_from_lmdb(self.env, 'latents')
|
| 43 |
+
self.max_pair = max_pair
|
| 44 |
+
|
| 45 |
+
def __len__(self):
|
| 46 |
+
return min(self.latents_shape[0], self.max_pair)
|
| 47 |
+
|
| 48 |
+
def __getitem__(self, idx):
|
| 49 |
+
"""
|
| 50 |
+
Outputs:
|
| 51 |
+
- prompts: List of Strings
|
| 52 |
+
- latents: Tensor of shape (num_denoising_steps, num_frames, num_channels, height, width). It is ordered from pure noise to clean image.
|
| 53 |
+
"""
|
| 54 |
+
latents = retrieve_row_from_lmdb(
|
| 55 |
+
self.env,
|
| 56 |
+
"latents", np.float16, idx, shape=self.latents_shape[1:]
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
if len(latents.shape) == 4:
|
| 60 |
+
latents = latents[None, ...]
|
| 61 |
+
|
| 62 |
+
prompts = retrieve_row_from_lmdb(
|
| 63 |
+
self.env,
|
| 64 |
+
"prompts", str, idx
|
| 65 |
+
)
|
| 66 |
+
return {
|
| 67 |
+
"prompts": prompts,
|
| 68 |
+
"ode_latent": torch.tensor(latents, dtype=torch.float32)
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class ShardingLMDBDataset(Dataset):
|
| 73 |
+
def __init__(self, data_path: str, max_pair: int = int(1e8)):
|
| 74 |
+
self.envs = []
|
| 75 |
+
self.index = []
|
| 76 |
+
|
| 77 |
+
for fname in sorted(os.listdir(data_path)):
|
| 78 |
+
path = os.path.join(data_path, fname)
|
| 79 |
+
env = lmdb.open(path,
|
| 80 |
+
readonly=True,
|
| 81 |
+
lock=False,
|
| 82 |
+
readahead=False,
|
| 83 |
+
meminit=False)
|
| 84 |
+
self.envs.append(env)
|
| 85 |
+
|
| 86 |
+
self.latents_shape = [None] * len(self.envs)
|
| 87 |
+
for shard_id, env in enumerate(self.envs):
|
| 88 |
+
self.latents_shape[shard_id] = get_array_shape_from_lmdb(env, 'latents')
|
| 89 |
+
for local_i in range(self.latents_shape[shard_id][0]):
|
| 90 |
+
self.index.append((shard_id, local_i))
|
| 91 |
+
|
| 92 |
+
# print("shard_id ", shard_id, " local_i ", local_i)
|
| 93 |
+
|
| 94 |
+
self.max_pair = max_pair
|
| 95 |
+
|
| 96 |
+
def __len__(self):
|
| 97 |
+
return len(self.index)
|
| 98 |
+
|
| 99 |
+
def __getitem__(self, idx):
|
| 100 |
+
"""
|
| 101 |
+
Outputs:
|
| 102 |
+
- prompts: List of Strings
|
| 103 |
+
- latents: Tensor of shape (num_denoising_steps, num_frames, num_channels, height, width). It is ordered from pure noise to clean image.
|
| 104 |
+
"""
|
| 105 |
+
shard_id, local_idx = self.index[idx]
|
| 106 |
+
|
| 107 |
+
latents = retrieve_row_from_lmdb(
|
| 108 |
+
self.envs[shard_id],
|
| 109 |
+
"latents", np.float16, local_idx,
|
| 110 |
+
shape=self.latents_shape[shard_id][1:]
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
if len(latents.shape) == 4:
|
| 114 |
+
latents = latents[None, ...]
|
| 115 |
+
|
| 116 |
+
prompts = retrieve_row_from_lmdb(
|
| 117 |
+
self.envs[shard_id],
|
| 118 |
+
"prompts", str, local_idx
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
return {
|
| 122 |
+
"prompts": prompts,
|
| 123 |
+
"ode_latent": torch.tensor(latents, dtype=torch.float32)
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class TextImagePairDataset(Dataset):
|
| 128 |
+
def __init__(
|
| 129 |
+
self,
|
| 130 |
+
data_dir,
|
| 131 |
+
transform=None,
|
| 132 |
+
eval_first_n=-1,
|
| 133 |
+
pad_to_multiple_of=None
|
| 134 |
+
):
|
| 135 |
+
"""
|
| 136 |
+
Args:
|
| 137 |
+
data_dir (str): Path to the directory containing:
|
| 138 |
+
- target_crop_info_*.json (metadata file)
|
| 139 |
+
- */ (subdirectory containing images with matching aspect ratio)
|
| 140 |
+
transform (callable, optional): Optional transform to be applied on the image
|
| 141 |
+
"""
|
| 142 |
+
self.transform = transform
|
| 143 |
+
data_dir = Path(data_dir)
|
| 144 |
+
|
| 145 |
+
# Find the metadata JSON file
|
| 146 |
+
metadata_files = list(data_dir.glob('target_crop_info_*.json'))
|
| 147 |
+
if not metadata_files:
|
| 148 |
+
raise FileNotFoundError(f"No metadata file found in {data_dir}")
|
| 149 |
+
if len(metadata_files) > 1:
|
| 150 |
+
raise ValueError(f"Multiple metadata files found in {data_dir}")
|
| 151 |
+
|
| 152 |
+
metadata_path = metadata_files[0]
|
| 153 |
+
# Extract aspect ratio from metadata filename (e.g. target_crop_info_26-15.json -> 26-15)
|
| 154 |
+
aspect_ratio = metadata_path.stem.split('_')[-1]
|
| 155 |
+
|
| 156 |
+
# Use aspect ratio subfolder for images
|
| 157 |
+
self.image_dir = data_dir / aspect_ratio
|
| 158 |
+
if not self.image_dir.exists():
|
| 159 |
+
raise FileNotFoundError(f"Image directory not found: {self.image_dir}")
|
| 160 |
+
|
| 161 |
+
# Load metadata
|
| 162 |
+
with open(metadata_path, 'r') as f:
|
| 163 |
+
self.metadata = json.load(f)
|
| 164 |
+
|
| 165 |
+
eval_first_n = eval_first_n if eval_first_n != -1 else len(self.metadata)
|
| 166 |
+
self.metadata = self.metadata[:eval_first_n]
|
| 167 |
+
|
| 168 |
+
# Verify all images exist
|
| 169 |
+
for item in self.metadata:
|
| 170 |
+
image_path = self.image_dir / item['file_name']
|
| 171 |
+
if not image_path.exists():
|
| 172 |
+
raise FileNotFoundError(f"Image not found: {image_path}")
|
| 173 |
+
|
| 174 |
+
self.dummy_prompt = "DUMMY PROMPT"
|
| 175 |
+
self.pre_pad_len = len(self.metadata)
|
| 176 |
+
if pad_to_multiple_of is not None and len(self.metadata) % pad_to_multiple_of != 0:
|
| 177 |
+
# Duplicate the last entry
|
| 178 |
+
self.metadata += [self.metadata[-1]] * (
|
| 179 |
+
pad_to_multiple_of - len(self.metadata) % pad_to_multiple_of
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
def __len__(self):
|
| 183 |
+
return len(self.metadata)
|
| 184 |
+
|
| 185 |
+
def __getitem__(self, idx):
|
| 186 |
+
"""
|
| 187 |
+
Returns:
|
| 188 |
+
dict: A dictionary containing:
|
| 189 |
+
- image: PIL Image
|
| 190 |
+
- caption: str
|
| 191 |
+
- target_bbox: list of int [x1, y1, x2, y2]
|
| 192 |
+
- target_ratio: str
|
| 193 |
+
- type: str
|
| 194 |
+
- origin_size: tuple of int (width, height)
|
| 195 |
+
"""
|
| 196 |
+
item = self.metadata[idx]
|
| 197 |
+
|
| 198 |
+
# Load image
|
| 199 |
+
image_path = self.image_dir / item['file_name']
|
| 200 |
+
image = Image.open(image_path).convert('RGB')
|
| 201 |
+
|
| 202 |
+
# Apply transform if specified
|
| 203 |
+
if self.transform:
|
| 204 |
+
image = self.transform(image)
|
| 205 |
+
|
| 206 |
+
return {
|
| 207 |
+
'image': image,
|
| 208 |
+
'prompts': item['caption'],
|
| 209 |
+
'target_bbox': item['target_crop']['target_bbox'],
|
| 210 |
+
'target_ratio': item['target_crop']['target_ratio'],
|
| 211 |
+
'type': item['type'],
|
| 212 |
+
'origin_size': (item['origin_width'], item['origin_height']),
|
| 213 |
+
'idx': idx
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def cycle(dl):
|
| 218 |
+
while True:
|
| 219 |
+
for data in dl:
|
| 220 |
+
yield data
|
utils/distributed.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import timedelta
|
| 2 |
+
from functools import partial
|
| 3 |
+
import os
|
| 4 |
+
import torch
|
| 5 |
+
import torch.distributed as dist
|
| 6 |
+
from torch.distributed.fsdp import FullStateDictConfig, FullyShardedDataParallel as FSDP, MixedPrecision, ShardingStrategy, StateDictType
|
| 7 |
+
from torch.distributed.fsdp.api import CPUOffload
|
| 8 |
+
from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy, transformer_auto_wrap_policy
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def fsdp_state_dict(model):
|
| 12 |
+
fsdp_fullstate_save_policy = FullStateDictConfig(
|
| 13 |
+
offload_to_cpu=True, rank0_only=True
|
| 14 |
+
)
|
| 15 |
+
with FSDP.state_dict_type(
|
| 16 |
+
model, StateDictType.FULL_STATE_DICT, fsdp_fullstate_save_policy
|
| 17 |
+
):
|
| 18 |
+
checkpoint = model.state_dict()
|
| 19 |
+
|
| 20 |
+
return checkpoint
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def fsdp_wrap(module, sharding_strategy="full", mixed_precision=False, wrap_strategy="size", min_num_params=int(5e7), transformer_module=None, cpu_offload=False):
|
| 24 |
+
if mixed_precision:
|
| 25 |
+
mixed_precision_policy = MixedPrecision(
|
| 26 |
+
param_dtype=torch.bfloat16,
|
| 27 |
+
reduce_dtype=torch.float32,
|
| 28 |
+
buffer_dtype=torch.float32,
|
| 29 |
+
cast_forward_inputs=False
|
| 30 |
+
)
|
| 31 |
+
else:
|
| 32 |
+
mixed_precision_policy = None
|
| 33 |
+
|
| 34 |
+
if wrap_strategy == "transformer":
|
| 35 |
+
auto_wrap_policy = partial(
|
| 36 |
+
transformer_auto_wrap_policy,
|
| 37 |
+
transformer_layer_cls=transformer_module
|
| 38 |
+
)
|
| 39 |
+
elif wrap_strategy == "size":
|
| 40 |
+
auto_wrap_policy = partial(
|
| 41 |
+
size_based_auto_wrap_policy,
|
| 42 |
+
min_num_params=min_num_params
|
| 43 |
+
)
|
| 44 |
+
else:
|
| 45 |
+
raise ValueError(f"Invalid wrap strategy: {wrap_strategy}")
|
| 46 |
+
|
| 47 |
+
os.environ["NCCL_CROSS_NIC"] = "1"
|
| 48 |
+
|
| 49 |
+
sharding_strategy = {
|
| 50 |
+
"full": ShardingStrategy.FULL_SHARD,
|
| 51 |
+
"hybrid_full": ShardingStrategy.HYBRID_SHARD,
|
| 52 |
+
"hybrid_zero2": ShardingStrategy._HYBRID_SHARD_ZERO2,
|
| 53 |
+
"no_shard": ShardingStrategy.NO_SHARD,
|
| 54 |
+
}[sharding_strategy]
|
| 55 |
+
|
| 56 |
+
module = FSDP(
|
| 57 |
+
module,
|
| 58 |
+
auto_wrap_policy=auto_wrap_policy,
|
| 59 |
+
sharding_strategy=sharding_strategy,
|
| 60 |
+
mixed_precision=mixed_precision_policy,
|
| 61 |
+
device_id=torch.cuda.current_device(),
|
| 62 |
+
limit_all_gathers=True,
|
| 63 |
+
use_orig_params=True,
|
| 64 |
+
cpu_offload=CPUOffload(offload_params=cpu_offload),
|
| 65 |
+
sync_module_states=False # Load ckpt on rank 0 and sync to other ranks
|
| 66 |
+
)
|
| 67 |
+
return module
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def barrier():
|
| 71 |
+
if dist.is_initialized():
|
| 72 |
+
dist.barrier()
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def launch_distributed_job(backend: str = "nccl"):
|
| 76 |
+
rank = int(os.environ["RANK"])
|
| 77 |
+
local_rank = int(os.environ["LOCAL_RANK"])
|
| 78 |
+
world_size = int(os.environ["WORLD_SIZE"])
|
| 79 |
+
host = os.environ["MASTER_ADDR"]
|
| 80 |
+
port = int(os.environ["MASTER_PORT"])
|
| 81 |
+
|
| 82 |
+
if ":" in host: # IPv6
|
| 83 |
+
init_method = f"tcp://[{host}]:{port}"
|
| 84 |
+
else: # IPv4
|
| 85 |
+
init_method = f"tcp://{host}:{port}"
|
| 86 |
+
dist.init_process_group(rank=rank, world_size=world_size, backend=backend,
|
| 87 |
+
init_method=init_method, timeout=timedelta(minutes=30))
|
| 88 |
+
torch.cuda.set_device(local_rank)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class EMA_FSDP:
|
| 92 |
+
def __init__(self, fsdp_module: torch.nn.Module, decay: float = 0.999):
|
| 93 |
+
self.decay = decay
|
| 94 |
+
self.shadow = {}
|
| 95 |
+
self._init_shadow(fsdp_module)
|
| 96 |
+
|
| 97 |
+
@torch.no_grad()
|
| 98 |
+
def _init_shadow(self, fsdp_module):
|
| 99 |
+
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
|
| 100 |
+
with FSDP.summon_full_params(fsdp_module, writeback=False):
|
| 101 |
+
for n, p in fsdp_module.module.named_parameters():
|
| 102 |
+
self.shadow[n] = p.detach().clone().float().cpu()
|
| 103 |
+
|
| 104 |
+
@torch.no_grad()
|
| 105 |
+
def update(self, fsdp_module):
|
| 106 |
+
d = self.decay
|
| 107 |
+
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
|
| 108 |
+
with FSDP.summon_full_params(fsdp_module, writeback=False):
|
| 109 |
+
for n, p in fsdp_module.module.named_parameters():
|
| 110 |
+
self.shadow[n].mul_(d).add_(p.detach().float().cpu(), alpha=1. - d)
|
| 111 |
+
|
| 112 |
+
# Optional helpers ---------------------------------------------------
|
| 113 |
+
def state_dict(self):
|
| 114 |
+
return self.shadow # picklable
|
| 115 |
+
|
| 116 |
+
def load_state_dict(self, sd):
|
| 117 |
+
self.shadow = {k: v.clone() for k, v in sd.items()}
|
| 118 |
+
|
| 119 |
+
def copy_to(self, fsdp_module):
|
| 120 |
+
# load EMA weights into an (unwrapped) copy of the generator
|
| 121 |
+
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
|
| 122 |
+
with FSDP.summon_full_params(fsdp_module, writeback=True):
|
| 123 |
+
for n, p in fsdp_module.module.named_parameters():
|
| 124 |
+
if n in self.shadow:
|
| 125 |
+
p.data.copy_(self.shadow[n].to(p.dtype, device=p.device))
|
utils/lmdb.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def get_array_shape_from_lmdb(env, array_name):
|
| 5 |
+
with env.begin() as txn:
|
| 6 |
+
image_shape = txn.get(f"{array_name}_shape".encode()).decode()
|
| 7 |
+
image_shape = tuple(map(int, image_shape.split()))
|
| 8 |
+
return image_shape
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def store_arrays_to_lmdb(env, arrays_dict, start_index=0):
|
| 12 |
+
"""
|
| 13 |
+
Store rows of multiple numpy arrays in a single LMDB.
|
| 14 |
+
Each row is stored separately with a naming convention.
|
| 15 |
+
"""
|
| 16 |
+
with env.begin(write=True) as txn:
|
| 17 |
+
for array_name, array in arrays_dict.items():
|
| 18 |
+
for i, row in enumerate(array):
|
| 19 |
+
# Convert row to bytes
|
| 20 |
+
if isinstance(row, str):
|
| 21 |
+
row_bytes = row.encode()
|
| 22 |
+
else:
|
| 23 |
+
row_bytes = row.tobytes()
|
| 24 |
+
|
| 25 |
+
data_key = f'{array_name}_{start_index + i}_data'.encode()
|
| 26 |
+
|
| 27 |
+
txn.put(data_key, row_bytes)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def process_data_dict(data_dict, seen_prompts):
|
| 31 |
+
output_dict = {}
|
| 32 |
+
|
| 33 |
+
all_videos = []
|
| 34 |
+
all_prompts = []
|
| 35 |
+
for prompt, video in data_dict.items():
|
| 36 |
+
if prompt in seen_prompts:
|
| 37 |
+
continue
|
| 38 |
+
else:
|
| 39 |
+
seen_prompts.add(prompt)
|
| 40 |
+
|
| 41 |
+
video = video.half().numpy()
|
| 42 |
+
all_videos.append(video)
|
| 43 |
+
all_prompts.append(prompt)
|
| 44 |
+
|
| 45 |
+
if len(all_videos) == 0:
|
| 46 |
+
return {"latents": np.array([]), "prompts": np.array([])}
|
| 47 |
+
|
| 48 |
+
all_videos = np.concatenate(all_videos, axis=0)
|
| 49 |
+
|
| 50 |
+
output_dict['latents'] = all_videos
|
| 51 |
+
output_dict['prompts'] = np.array(all_prompts)
|
| 52 |
+
|
| 53 |
+
return output_dict
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def retrieve_row_from_lmdb(lmdb_env, array_name, dtype, row_index, shape=None):
|
| 57 |
+
"""
|
| 58 |
+
Retrieve a specific row from a specific array in the LMDB.
|
| 59 |
+
"""
|
| 60 |
+
data_key = f'{array_name}_{row_index}_data'.encode()
|
| 61 |
+
|
| 62 |
+
with lmdb_env.begin() as txn:
|
| 63 |
+
row_bytes = txn.get(data_key)
|
| 64 |
+
|
| 65 |
+
if dtype == str:
|
| 66 |
+
array = row_bytes.decode()
|
| 67 |
+
else:
|
| 68 |
+
array = np.frombuffer(row_bytes, dtype=dtype)
|
| 69 |
+
|
| 70 |
+
if shape is not None and len(shape) > 0:
|
| 71 |
+
array = array.reshape(shape)
|
| 72 |
+
return array
|
utils/loss.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import ABC, abstractmethod
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class DenoisingLoss(ABC):
|
| 6 |
+
@abstractmethod
|
| 7 |
+
def __call__(
|
| 8 |
+
self, x: torch.Tensor, x_pred: torch.Tensor,
|
| 9 |
+
noise: torch.Tensor, noise_pred: torch.Tensor,
|
| 10 |
+
alphas_cumprod: torch.Tensor,
|
| 11 |
+
timestep: torch.Tensor,
|
| 12 |
+
**kwargs
|
| 13 |
+
) -> torch.Tensor:
|
| 14 |
+
"""
|
| 15 |
+
Base class for denoising loss.
|
| 16 |
+
Input:
|
| 17 |
+
- x: the clean data with shape [B, F, C, H, W]
|
| 18 |
+
- x_pred: the predicted clean data with shape [B, F, C, H, W]
|
| 19 |
+
- noise: the noise with shape [B, F, C, H, W]
|
| 20 |
+
- noise_pred: the predicted noise with shape [B, F, C, H, W]
|
| 21 |
+
- alphas_cumprod: the cumulative product of alphas (defining the noise schedule) with shape [T]
|
| 22 |
+
- timestep: the current timestep with shape [B, F]
|
| 23 |
+
"""
|
| 24 |
+
pass
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class X0PredLoss(DenoisingLoss):
|
| 28 |
+
def __call__(
|
| 29 |
+
self, x: torch.Tensor, x_pred: torch.Tensor,
|
| 30 |
+
noise: torch.Tensor, noise_pred: torch.Tensor,
|
| 31 |
+
alphas_cumprod: torch.Tensor,
|
| 32 |
+
timestep: torch.Tensor,
|
| 33 |
+
**kwargs
|
| 34 |
+
) -> torch.Tensor:
|
| 35 |
+
return torch.mean((x - x_pred) ** 2)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class VPredLoss(DenoisingLoss):
|
| 39 |
+
def __call__(
|
| 40 |
+
self, x: torch.Tensor, x_pred: torch.Tensor,
|
| 41 |
+
noise: torch.Tensor, noise_pred: torch.Tensor,
|
| 42 |
+
alphas_cumprod: torch.Tensor,
|
| 43 |
+
timestep: torch.Tensor,
|
| 44 |
+
**kwargs
|
| 45 |
+
) -> torch.Tensor:
|
| 46 |
+
weights = 1 / (1 - alphas_cumprod[timestep].reshape(*timestep.shape, 1, 1, 1))
|
| 47 |
+
return torch.mean(weights * (x - x_pred) ** 2)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class NoisePredLoss(DenoisingLoss):
|
| 51 |
+
def __call__(
|
| 52 |
+
self, x: torch.Tensor, x_pred: torch.Tensor,
|
| 53 |
+
noise: torch.Tensor, noise_pred: torch.Tensor,
|
| 54 |
+
alphas_cumprod: torch.Tensor,
|
| 55 |
+
timestep: torch.Tensor,
|
| 56 |
+
**kwargs
|
| 57 |
+
) -> torch.Tensor:
|
| 58 |
+
return torch.mean((noise - noise_pred) ** 2)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class FlowPredLoss(DenoisingLoss):
|
| 62 |
+
def __call__(
|
| 63 |
+
self, x: torch.Tensor, x_pred: torch.Tensor,
|
| 64 |
+
noise: torch.Tensor, noise_pred: torch.Tensor,
|
| 65 |
+
alphas_cumprod: torch.Tensor,
|
| 66 |
+
timestep: torch.Tensor,
|
| 67 |
+
**kwargs
|
| 68 |
+
) -> torch.Tensor:
|
| 69 |
+
return torch.mean((kwargs["flow_pred"] - (noise - x)) ** 2)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
NAME_TO_CLASS = {
|
| 73 |
+
"x0": X0PredLoss,
|
| 74 |
+
"v": VPredLoss,
|
| 75 |
+
"noise": NoisePredLoss,
|
| 76 |
+
"flow": FlowPredLoss
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def get_denoising_loss(loss_type: str) -> DenoisingLoss:
|
| 81 |
+
return NAME_TO_CLASS[loss_type]
|
utils/misc.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import random
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def set_seed(seed: int, deterministic: bool = False):
|
| 7 |
+
"""
|
| 8 |
+
Helper function for reproducible behavior to set the seed in `random`, `numpy`, `torch`.
|
| 9 |
+
|
| 10 |
+
Args:
|
| 11 |
+
seed (`int`):
|
| 12 |
+
The seed to set.
|
| 13 |
+
deterministic (`bool`, *optional*, defaults to `False`):
|
| 14 |
+
Whether to use deterministic algorithms where available. Can slow down training.
|
| 15 |
+
"""
|
| 16 |
+
random.seed(seed)
|
| 17 |
+
np.random.seed(seed)
|
| 18 |
+
torch.manual_seed(seed)
|
| 19 |
+
torch.cuda.manual_seed_all(seed)
|
| 20 |
+
|
| 21 |
+
if deterministic:
|
| 22 |
+
torch.use_deterministic_algorithms(True)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def merge_dict_list(dict_list):
|
| 26 |
+
if len(dict_list) == 1:
|
| 27 |
+
return dict_list[0]
|
| 28 |
+
|
| 29 |
+
merged_dict = {}
|
| 30 |
+
for k, v in dict_list[0].items():
|
| 31 |
+
if isinstance(v, torch.Tensor):
|
| 32 |
+
if v.ndim == 0:
|
| 33 |
+
merged_dict[k] = torch.stack([d[k] for d in dict_list], dim=0)
|
| 34 |
+
else:
|
| 35 |
+
merged_dict[k] = torch.cat([d[k] for d in dict_list], dim=0)
|
| 36 |
+
else:
|
| 37 |
+
# for non-tensor values, we just copy the value from the first item
|
| 38 |
+
merged_dict[k] = v
|
| 39 |
+
return merged_dict
|
utils/scheduler.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import abstractmethod, ABC
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class SchedulerInterface(ABC):
|
| 6 |
+
"""
|
| 7 |
+
Base class for diffusion noise schedule.
|
| 8 |
+
"""
|
| 9 |
+
alphas_cumprod: torch.Tensor # [T], alphas for defining the noise schedule
|
| 10 |
+
|
| 11 |
+
@abstractmethod
|
| 12 |
+
def add_noise(
|
| 13 |
+
self, clean_latent: torch.Tensor,
|
| 14 |
+
noise: torch.Tensor, timestep: torch.Tensor
|
| 15 |
+
):
|
| 16 |
+
"""
|
| 17 |
+
Diffusion forward corruption process.
|
| 18 |
+
Input:
|
| 19 |
+
- clean_latent: the clean latent with shape [B, C, H, W]
|
| 20 |
+
- noise: the noise with shape [B, C, H, W]
|
| 21 |
+
- timestep: the timestep with shape [B]
|
| 22 |
+
Output: the corrupted latent with shape [B, C, H, W]
|
| 23 |
+
"""
|
| 24 |
+
pass
|
| 25 |
+
|
| 26 |
+
def convert_x0_to_noise(
|
| 27 |
+
self, x0: torch.Tensor, xt: torch.Tensor,
|
| 28 |
+
timestep: torch.Tensor
|
| 29 |
+
) -> torch.Tensor:
|
| 30 |
+
"""
|
| 31 |
+
Convert the diffusion network's x0 prediction to noise predidction.
|
| 32 |
+
x0: the predicted clean data with shape [B, C, H, W]
|
| 33 |
+
xt: the input noisy data with shape [B, C, H, W]
|
| 34 |
+
timestep: the timestep with shape [B]
|
| 35 |
+
|
| 36 |
+
noise = (xt-sqrt(alpha_t)*x0) / sqrt(beta_t) (eq 11 in https://arxiv.org/abs/2311.18828)
|
| 37 |
+
"""
|
| 38 |
+
# use higher precision for calculations
|
| 39 |
+
original_dtype = x0.dtype
|
| 40 |
+
x0, xt, alphas_cumprod = map(
|
| 41 |
+
lambda x: x.double().to(x0.device), [x0, xt,
|
| 42 |
+
self.alphas_cumprod]
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
alpha_prod_t = alphas_cumprod[timestep].reshape(-1, 1, 1, 1)
|
| 46 |
+
beta_prod_t = 1 - alpha_prod_t
|
| 47 |
+
|
| 48 |
+
noise_pred = (xt - alpha_prod_t **
|
| 49 |
+
(0.5) * x0) / beta_prod_t ** (0.5)
|
| 50 |
+
return noise_pred.to(original_dtype)
|
| 51 |
+
|
| 52 |
+
def convert_noise_to_x0(
|
| 53 |
+
self, noise: torch.Tensor, xt: torch.Tensor,
|
| 54 |
+
timestep: torch.Tensor
|
| 55 |
+
) -> torch.Tensor:
|
| 56 |
+
"""
|
| 57 |
+
Convert the diffusion network's noise prediction to x0 predidction.
|
| 58 |
+
noise: the predicted noise with shape [B, C, H, W]
|
| 59 |
+
xt: the input noisy data with shape [B, C, H, W]
|
| 60 |
+
timestep: the timestep with shape [B]
|
| 61 |
+
|
| 62 |
+
x0 = (x_t - sqrt(beta_t) * noise) / sqrt(alpha_t) (eq 11 in https://arxiv.org/abs/2311.18828)
|
| 63 |
+
"""
|
| 64 |
+
# use higher precision for calculations
|
| 65 |
+
original_dtype = noise.dtype
|
| 66 |
+
noise, xt, alphas_cumprod = map(
|
| 67 |
+
lambda x: x.double().to(noise.device), [noise, xt,
|
| 68 |
+
self.alphas_cumprod]
|
| 69 |
+
)
|
| 70 |
+
alpha_prod_t = alphas_cumprod[timestep].reshape(-1, 1, 1, 1)
|
| 71 |
+
beta_prod_t = 1 - alpha_prod_t
|
| 72 |
+
|
| 73 |
+
x0_pred = (xt - beta_prod_t **
|
| 74 |
+
(0.5) * noise) / alpha_prod_t ** (0.5)
|
| 75 |
+
return x0_pred.to(original_dtype)
|
| 76 |
+
|
| 77 |
+
def convert_velocity_to_x0(
|
| 78 |
+
self, velocity: torch.Tensor, xt: torch.Tensor,
|
| 79 |
+
timestep: torch.Tensor
|
| 80 |
+
) -> torch.Tensor:
|
| 81 |
+
"""
|
| 82 |
+
Convert the diffusion network's velocity prediction to x0 predidction.
|
| 83 |
+
velocity: the predicted noise with shape [B, C, H, W]
|
| 84 |
+
xt: the input noisy data with shape [B, C, H, W]
|
| 85 |
+
timestep: the timestep with shape [B]
|
| 86 |
+
|
| 87 |
+
v = sqrt(alpha_t) * noise - sqrt(beta_t) x0
|
| 88 |
+
noise = (xt-sqrt(alpha_t)*x0) / sqrt(beta_t)
|
| 89 |
+
given v, x_t, we have
|
| 90 |
+
x0 = sqrt(alpha_t) * x_t - sqrt(beta_t) * v
|
| 91 |
+
see derivations https://chatgpt.com/share/679fb6c8-3a30-8008-9b0e-d1ae892dac56
|
| 92 |
+
"""
|
| 93 |
+
# use higher precision for calculations
|
| 94 |
+
original_dtype = velocity.dtype
|
| 95 |
+
velocity, xt, alphas_cumprod = map(
|
| 96 |
+
lambda x: x.double().to(velocity.device), [velocity, xt,
|
| 97 |
+
self.alphas_cumprod]
|
| 98 |
+
)
|
| 99 |
+
alpha_prod_t = alphas_cumprod[timestep].reshape(-1, 1, 1, 1)
|
| 100 |
+
beta_prod_t = 1 - alpha_prod_t
|
| 101 |
+
|
| 102 |
+
x0_pred = (alpha_prod_t ** 0.5) * xt - (beta_prod_t ** 0.5) * velocity
|
| 103 |
+
return x0_pred.to(original_dtype)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
class FlowMatchScheduler():
|
| 107 |
+
|
| 108 |
+
def __init__(self, num_inference_steps=100, num_train_timesteps=1000, shift=3.0, sigma_max=1.0, sigma_min=0.003 / 1.002, inverse_timesteps=False, extra_one_step=False, reverse_sigmas=False):
|
| 109 |
+
self.num_train_timesteps = num_train_timesteps
|
| 110 |
+
self.shift = shift
|
| 111 |
+
self.sigma_max = sigma_max
|
| 112 |
+
self.sigma_min = sigma_min
|
| 113 |
+
self.inverse_timesteps = inverse_timesteps
|
| 114 |
+
self.extra_one_step = extra_one_step
|
| 115 |
+
self.reverse_sigmas = reverse_sigmas
|
| 116 |
+
self.set_timesteps(num_inference_steps)
|
| 117 |
+
|
| 118 |
+
def set_timesteps(self, num_inference_steps=100, denoising_strength=1.0, training=False):
|
| 119 |
+
sigma_start = self.sigma_min + \
|
| 120 |
+
(self.sigma_max - self.sigma_min) * denoising_strength
|
| 121 |
+
if self.extra_one_step:
|
| 122 |
+
self.sigmas = torch.linspace(
|
| 123 |
+
sigma_start, self.sigma_min, num_inference_steps + 1)[:-1]
|
| 124 |
+
else:
|
| 125 |
+
self.sigmas = torch.linspace(
|
| 126 |
+
sigma_start, self.sigma_min, num_inference_steps)
|
| 127 |
+
if self.inverse_timesteps:
|
| 128 |
+
self.sigmas = torch.flip(self.sigmas, dims=[0])
|
| 129 |
+
self.sigmas = self.shift * self.sigmas / \
|
| 130 |
+
(1 + (self.shift - 1) * self.sigmas)
|
| 131 |
+
if self.reverse_sigmas:
|
| 132 |
+
self.sigmas = 1 - self.sigmas
|
| 133 |
+
self.timesteps = self.sigmas * self.num_train_timesteps
|
| 134 |
+
if training:
|
| 135 |
+
x = self.timesteps
|
| 136 |
+
y = torch.exp(-2 * ((x - num_inference_steps / 2) /
|
| 137 |
+
num_inference_steps) ** 2)
|
| 138 |
+
y_shifted = y - y.min()
|
| 139 |
+
bsmntw_weighing = y_shifted * \
|
| 140 |
+
(num_inference_steps / y_shifted.sum())
|
| 141 |
+
self.linear_timesteps_weights = bsmntw_weighing
|
| 142 |
+
|
| 143 |
+
def step(self, model_output, timestep, sample, to_final=False):
|
| 144 |
+
if timestep.ndim == 2:
|
| 145 |
+
timestep = timestep.flatten(0, 1)
|
| 146 |
+
self.sigmas = self.sigmas.to(model_output.device)
|
| 147 |
+
self.timesteps = self.timesteps.to(model_output.device)
|
| 148 |
+
timestep_id = torch.argmin(
|
| 149 |
+
(self.timesteps.unsqueeze(0) - timestep.unsqueeze(1)).abs(), dim=1)
|
| 150 |
+
sigma = self.sigmas[timestep_id].reshape(-1, 1, 1, 1)
|
| 151 |
+
if to_final or (timestep_id + 1 >= len(self.timesteps)).any():
|
| 152 |
+
sigma_ = 1 if (
|
| 153 |
+
self.inverse_timesteps or self.reverse_sigmas) else 0
|
| 154 |
+
else:
|
| 155 |
+
sigma_ = self.sigmas[timestep_id + 1].reshape(-1, 1, 1, 1)
|
| 156 |
+
prev_sample = sample + model_output * (sigma_ - sigma)
|
| 157 |
+
return prev_sample
|
| 158 |
+
|
| 159 |
+
def add_noise(self, original_samples, noise, timestep):
|
| 160 |
+
"""
|
| 161 |
+
Diffusion forward corruption process.
|
| 162 |
+
Input:
|
| 163 |
+
- clean_latent: the clean latent with shape [B*T, C, H, W]
|
| 164 |
+
- noise: the noise with shape [B*T, C, H, W]
|
| 165 |
+
- timestep: the timestep with shape [B*T]
|
| 166 |
+
Output: the corrupted latent with shape [B*T, C, H, W]
|
| 167 |
+
"""
|
| 168 |
+
if timestep.ndim == 2:
|
| 169 |
+
timestep = timestep.flatten(0, 1)
|
| 170 |
+
self.sigmas = self.sigmas.to(noise.device)
|
| 171 |
+
self.timesteps = self.timesteps.to(noise.device)
|
| 172 |
+
timestep_id = torch.argmin(
|
| 173 |
+
(self.timesteps.unsqueeze(0) - timestep.unsqueeze(1)).abs(), dim=1)
|
| 174 |
+
sigma = self.sigmas[timestep_id].reshape(-1, 1, 1, 1)
|
| 175 |
+
sample = (1 - sigma) * original_samples + sigma * noise
|
| 176 |
+
return sample.type_as(noise)
|
| 177 |
+
|
| 178 |
+
def training_target(self, sample, noise, timestep):
|
| 179 |
+
target = noise - sample
|
| 180 |
+
return target
|
| 181 |
+
|
| 182 |
+
def training_weight(self, timestep):
|
| 183 |
+
"""
|
| 184 |
+
Input:
|
| 185 |
+
- timestep: the timestep with shape [B*T]
|
| 186 |
+
Output: the corresponding weighting [B*T]
|
| 187 |
+
"""
|
| 188 |
+
if timestep.ndim == 2:
|
| 189 |
+
timestep = timestep.flatten(0, 1)
|
| 190 |
+
self.linear_timesteps_weights = self.linear_timesteps_weights.to(timestep.device)
|
| 191 |
+
timestep_id = torch.argmin(
|
| 192 |
+
(self.timesteps.unsqueeze(1) - timestep.unsqueeze(0)).abs(), dim=0)
|
| 193 |
+
weights = self.linear_timesteps_weights[timestep_id]
|
| 194 |
+
return weights
|
utils/wan_wrapper.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import types
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import List, Optional
|
| 4 |
+
import torch
|
| 5 |
+
from torch import nn
|
| 6 |
+
|
| 7 |
+
from utils.scheduler import SchedulerInterface, FlowMatchScheduler
|
| 8 |
+
from wan.modules.tokenizers import HuggingfaceTokenizer
|
| 9 |
+
from wan.modules.model import WanModel, RegisterTokens, GanAttentionBlock
|
| 10 |
+
from wan.modules.vae import _video_vae
|
| 11 |
+
from wan.modules.t5 import umt5_xxl
|
| 12 |
+
from wan.modules.causal_model import CausalWanModel
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def resolve_wan_model_path(model_name: str | Path) -> Path:
|
| 16 |
+
"""Resolve model names under wan_models while allowing an explicit fast path."""
|
| 17 |
+
path = Path(model_name).expanduser()
|
| 18 |
+
return path if path.is_absolute() else Path("wan_models") / path
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class WanTextEncoder(torch.nn.Module):
|
| 22 |
+
def __init__(self) -> None:
|
| 23 |
+
super().__init__()
|
| 24 |
+
|
| 25 |
+
self.text_encoder = umt5_xxl(
|
| 26 |
+
encoder_only=True,
|
| 27 |
+
return_tokenizer=False,
|
| 28 |
+
dtype=torch.float32,
|
| 29 |
+
device=torch.device('cpu')
|
| 30 |
+
).eval().requires_grad_(False)
|
| 31 |
+
self.text_encoder.load_state_dict(
|
| 32 |
+
torch.load("wan_models/Wan2.1-T2V-1.3B/models_t5_umt5-xxl-enc-bf16.pth",
|
| 33 |
+
map_location='cpu', weights_only=False)
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
self.tokenizer = HuggingfaceTokenizer(
|
| 37 |
+
name="wan_models/Wan2.1-T2V-1.3B/google/umt5-xxl/", seq_len=512, clean='whitespace')
|
| 38 |
+
|
| 39 |
+
@property
|
| 40 |
+
def device(self):
|
| 41 |
+
# Assume we are always on GPU
|
| 42 |
+
return torch.cuda.current_device()
|
| 43 |
+
|
| 44 |
+
def forward(self, text_prompts: List[str]) -> dict:
|
| 45 |
+
ids, mask = self.tokenizer(
|
| 46 |
+
text_prompts, return_mask=True, add_special_tokens=True)
|
| 47 |
+
ids = ids.to(self.device)
|
| 48 |
+
mask = mask.to(self.device)
|
| 49 |
+
seq_lens = mask.gt(0).sum(dim=1).long()
|
| 50 |
+
context = self.text_encoder(ids, mask)
|
| 51 |
+
|
| 52 |
+
for u, v in zip(context, seq_lens):
|
| 53 |
+
u[v:] = 0.0 # set padding to 0.0
|
| 54 |
+
|
| 55 |
+
return {
|
| 56 |
+
"prompt_embeds": context
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class WanVAEWrapper(torch.nn.Module):
|
| 61 |
+
def __init__(self):
|
| 62 |
+
super().__init__()
|
| 63 |
+
mean = [
|
| 64 |
+
-0.7571, -0.7089, -0.9113, 0.1075, -0.1745, 0.9653, -0.1517, 1.5508,
|
| 65 |
+
0.4134, -0.0715, 0.5517, -0.3632, -0.1922, -0.9497, 0.2503, -0.2921
|
| 66 |
+
]
|
| 67 |
+
std = [
|
| 68 |
+
2.8184, 1.4541, 2.3275, 2.6558, 1.2196, 1.7708, 2.6052, 2.0743,
|
| 69 |
+
3.2687, 2.1526, 2.8652, 1.5579, 1.6382, 1.1253, 2.8251, 1.9160
|
| 70 |
+
]
|
| 71 |
+
self.mean = torch.tensor(mean, dtype=torch.float32)
|
| 72 |
+
self.std = torch.tensor(std, dtype=torch.float32)
|
| 73 |
+
|
| 74 |
+
# init model
|
| 75 |
+
self.model = _video_vae(
|
| 76 |
+
pretrained_path="wan_models/Wan2.1-T2V-1.3B/Wan2.1_VAE.pth",
|
| 77 |
+
z_dim=16,
|
| 78 |
+
).eval().requires_grad_(False)
|
| 79 |
+
|
| 80 |
+
def encode_to_latent(self, pixel: torch.Tensor) -> torch.Tensor:
|
| 81 |
+
# pixel: [batch_size, num_channels, num_frames, height, width]
|
| 82 |
+
device, dtype = pixel.device, pixel.dtype
|
| 83 |
+
scale = [self.mean.to(device=device, dtype=dtype),
|
| 84 |
+
1.0 / self.std.to(device=device, dtype=dtype)]
|
| 85 |
+
|
| 86 |
+
output = [
|
| 87 |
+
self.model.encode(u.unsqueeze(0), scale).float().squeeze(0)
|
| 88 |
+
for u in pixel
|
| 89 |
+
]
|
| 90 |
+
output = torch.stack(output, dim=0)
|
| 91 |
+
# from [batch_size, num_channels, num_frames, height, width]
|
| 92 |
+
# to [batch_size, num_frames, num_channels, height, width]
|
| 93 |
+
output = output.permute(0, 2, 1, 3, 4)
|
| 94 |
+
return output
|
| 95 |
+
|
| 96 |
+
def decode_to_pixel(self, latent: torch.Tensor, use_cache: bool = False) -> torch.Tensor:
|
| 97 |
+
# from [batch_size, num_frames, num_channels, height, width]
|
| 98 |
+
# to [batch_size, num_channels, num_frames, height, width]
|
| 99 |
+
zs = latent.permute(0, 2, 1, 3, 4)
|
| 100 |
+
if use_cache:
|
| 101 |
+
assert latent.shape[0] == 1, "Batch size must be 1 when using cache"
|
| 102 |
+
|
| 103 |
+
device, dtype = latent.device, latent.dtype
|
| 104 |
+
scale = [self.mean.to(device=device, dtype=dtype),
|
| 105 |
+
1.0 / self.std.to(device=device, dtype=dtype)]
|
| 106 |
+
|
| 107 |
+
if use_cache:
|
| 108 |
+
decode_function = self.model.cached_decode
|
| 109 |
+
else:
|
| 110 |
+
decode_function = self.model.decode
|
| 111 |
+
|
| 112 |
+
output = []
|
| 113 |
+
for u in zs:
|
| 114 |
+
output.append(decode_function(u.unsqueeze(0), scale).float().clamp_(-1, 1).squeeze(0))
|
| 115 |
+
output = torch.stack(output, dim=0)
|
| 116 |
+
# from [batch_size, num_channels, num_frames, height, width]
|
| 117 |
+
# to [batch_size, num_frames, num_channels, height, width]
|
| 118 |
+
output = output.permute(0, 2, 1, 3, 4)
|
| 119 |
+
return output
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
class WanDiffusionWrapper(torch.nn.Module):
|
| 123 |
+
def __init__(
|
| 124 |
+
self,
|
| 125 |
+
model_name="Wan2.1-T2V-1.3B",
|
| 126 |
+
timestep_shift=8.0,
|
| 127 |
+
is_causal=False,
|
| 128 |
+
local_attn_size=-1,
|
| 129 |
+
sink_size=0
|
| 130 |
+
):
|
| 131 |
+
super().__init__()
|
| 132 |
+
|
| 133 |
+
model_path = resolve_wan_model_path(model_name)
|
| 134 |
+
if is_causal:
|
| 135 |
+
self.model = CausalWanModel.from_pretrained(
|
| 136 |
+
model_path, local_attn_size=local_attn_size, sink_size=sink_size)
|
| 137 |
+
else:
|
| 138 |
+
self.model = WanModel.from_pretrained(model_path)
|
| 139 |
+
self.model.eval()
|
| 140 |
+
|
| 141 |
+
# For non-causal diffusion, all frames share the same timestep
|
| 142 |
+
self.uniform_timestep = not is_causal
|
| 143 |
+
|
| 144 |
+
self.scheduler = FlowMatchScheduler(
|
| 145 |
+
shift=timestep_shift, sigma_min=0.0, extra_one_step=True
|
| 146 |
+
)
|
| 147 |
+
self.scheduler.set_timesteps(1000, training=True)
|
| 148 |
+
|
| 149 |
+
self.seq_len = 32760 # [1, 21, 16, 60, 104]
|
| 150 |
+
self.post_init()
|
| 151 |
+
|
| 152 |
+
def enable_gradient_checkpointing(self) -> None:
|
| 153 |
+
self.model.enable_gradient_checkpointing()
|
| 154 |
+
|
| 155 |
+
def adding_cls_branch(self, atten_dim=1536, num_class=4, time_embed_dim=0) -> None:
|
| 156 |
+
# NOTE: This is hard coded for WAN2.1-T2V-1.3B for now!!!!!!!!!!!!!!!!!!!!
|
| 157 |
+
self._cls_pred_branch = nn.Sequential(
|
| 158 |
+
# Input: [B, 384, 21, 60, 104]
|
| 159 |
+
nn.LayerNorm(atten_dim * 3 + time_embed_dim),
|
| 160 |
+
nn.Linear(atten_dim * 3 + time_embed_dim, 1536),
|
| 161 |
+
nn.SiLU(),
|
| 162 |
+
nn.Linear(atten_dim, num_class)
|
| 163 |
+
)
|
| 164 |
+
self._cls_pred_branch.requires_grad_(True)
|
| 165 |
+
num_registers = 3
|
| 166 |
+
self._register_tokens = RegisterTokens(num_registers=num_registers, dim=atten_dim)
|
| 167 |
+
self._register_tokens.requires_grad_(True)
|
| 168 |
+
|
| 169 |
+
gan_ca_blocks = []
|
| 170 |
+
for _ in range(num_registers):
|
| 171 |
+
block = GanAttentionBlock()
|
| 172 |
+
gan_ca_blocks.append(block)
|
| 173 |
+
self._gan_ca_blocks = nn.ModuleList(gan_ca_blocks)
|
| 174 |
+
self._gan_ca_blocks.requires_grad_(True)
|
| 175 |
+
# self.has_cls_branch = True
|
| 176 |
+
|
| 177 |
+
def _convert_flow_pred_to_x0(self, flow_pred: torch.Tensor, xt: torch.Tensor, timestep: torch.Tensor) -> torch.Tensor:
|
| 178 |
+
"""
|
| 179 |
+
Convert flow matching's prediction to x0 prediction.
|
| 180 |
+
flow_pred: the prediction with shape [B, C, H, W]
|
| 181 |
+
xt: the input noisy data with shape [B, C, H, W]
|
| 182 |
+
timestep: the timestep with shape [B]
|
| 183 |
+
|
| 184 |
+
pred = noise - x0
|
| 185 |
+
x_t = (1-sigma_t) * x0 + sigma_t * noise
|
| 186 |
+
we have x0 = x_t - sigma_t * pred
|
| 187 |
+
see derivations https://chatgpt.com/share/67bf8589-3d04-8008-bc6e-4cf1a24e2d0e
|
| 188 |
+
"""
|
| 189 |
+
# use higher precision for calculations
|
| 190 |
+
original_dtype = flow_pred.dtype
|
| 191 |
+
flow_pred, xt, sigmas, timesteps = map(
|
| 192 |
+
lambda x: x.double().to(flow_pred.device), [flow_pred, xt,
|
| 193 |
+
self.scheduler.sigmas,
|
| 194 |
+
self.scheduler.timesteps]
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
timestep_id = torch.argmin(
|
| 198 |
+
(timesteps.unsqueeze(0) - timestep.unsqueeze(1)).abs(), dim=1)
|
| 199 |
+
sigma_t = sigmas[timestep_id].reshape(-1, 1, 1, 1)
|
| 200 |
+
x0_pred = xt - sigma_t * flow_pred
|
| 201 |
+
return x0_pred.to(original_dtype)
|
| 202 |
+
|
| 203 |
+
@staticmethod
|
| 204 |
+
def _convert_x0_to_flow_pred(scheduler, x0_pred: torch.Tensor, xt: torch.Tensor, timestep: torch.Tensor) -> torch.Tensor:
|
| 205 |
+
"""
|
| 206 |
+
Convert x0 prediction to flow matching's prediction.
|
| 207 |
+
x0_pred: the x0 prediction with shape [B, C, H, W]
|
| 208 |
+
xt: the input noisy data with shape [B, C, H, W]
|
| 209 |
+
timestep: the timestep with shape [B]
|
| 210 |
+
|
| 211 |
+
pred = (x_t - x_0) / sigma_t
|
| 212 |
+
"""
|
| 213 |
+
# use higher precision for calculations
|
| 214 |
+
original_dtype = x0_pred.dtype
|
| 215 |
+
x0_pred, xt, sigmas, timesteps = map(
|
| 216 |
+
lambda x: x.double().to(x0_pred.device), [x0_pred, xt,
|
| 217 |
+
scheduler.sigmas,
|
| 218 |
+
scheduler.timesteps]
|
| 219 |
+
)
|
| 220 |
+
timestep_id = torch.argmin(
|
| 221 |
+
(timesteps.unsqueeze(0) - timestep.unsqueeze(1)).abs(), dim=1)
|
| 222 |
+
sigma_t = sigmas[timestep_id].reshape(-1, 1, 1, 1)
|
| 223 |
+
flow_pred = (xt - x0_pred) / sigma_t
|
| 224 |
+
return flow_pred.to(original_dtype)
|
| 225 |
+
|
| 226 |
+
def forward(
|
| 227 |
+
self,
|
| 228 |
+
noisy_image_or_video: torch.Tensor, conditional_dict: dict,
|
| 229 |
+
timestep: torch.Tensor, kv_cache: Optional[List[dict]] = None,
|
| 230 |
+
crossattn_cache: Optional[List[dict]] = None,
|
| 231 |
+
current_start: Optional[int] = None,
|
| 232 |
+
classify_mode: Optional[bool] = False,
|
| 233 |
+
concat_time_embeddings: Optional[bool] = False,
|
| 234 |
+
clean_x: Optional[torch.Tensor] = None,
|
| 235 |
+
aug_t: Optional[torch.Tensor] = None,
|
| 236 |
+
cache_start: Optional[int] = None
|
| 237 |
+
) -> torch.Tensor:
|
| 238 |
+
prompt_embeds = conditional_dict["prompt_embeds"]
|
| 239 |
+
|
| 240 |
+
# [B, F] -> [B]
|
| 241 |
+
if self.uniform_timestep:
|
| 242 |
+
input_timestep = timestep[:, 0]
|
| 243 |
+
else:
|
| 244 |
+
input_timestep = timestep
|
| 245 |
+
|
| 246 |
+
logits = None
|
| 247 |
+
# X0 prediction
|
| 248 |
+
if kv_cache is not None:
|
| 249 |
+
flow_pred = self.model(
|
| 250 |
+
noisy_image_or_video.permute(0, 2, 1, 3, 4),
|
| 251 |
+
t=input_timestep, context=prompt_embeds,
|
| 252 |
+
seq_len=self.seq_len,
|
| 253 |
+
kv_cache=kv_cache,
|
| 254 |
+
crossattn_cache=crossattn_cache,
|
| 255 |
+
current_start=current_start,
|
| 256 |
+
cache_start=cache_start
|
| 257 |
+
).permute(0, 2, 1, 3, 4)
|
| 258 |
+
else:
|
| 259 |
+
if clean_x is not None:
|
| 260 |
+
# teacher forcing
|
| 261 |
+
flow_pred = self.model(
|
| 262 |
+
noisy_image_or_video.permute(0, 2, 1, 3, 4),
|
| 263 |
+
t=input_timestep, context=prompt_embeds,
|
| 264 |
+
seq_len=self.seq_len,
|
| 265 |
+
clean_x=clean_x.permute(0, 2, 1, 3, 4),
|
| 266 |
+
aug_t=aug_t,
|
| 267 |
+
).permute(0, 2, 1, 3, 4)
|
| 268 |
+
else:
|
| 269 |
+
if classify_mode:
|
| 270 |
+
flow_pred, logits = self.model(
|
| 271 |
+
noisy_image_or_video.permute(0, 2, 1, 3, 4),
|
| 272 |
+
t=input_timestep, context=prompt_embeds,
|
| 273 |
+
seq_len=self.seq_len,
|
| 274 |
+
classify_mode=True,
|
| 275 |
+
register_tokens=self._register_tokens,
|
| 276 |
+
cls_pred_branch=self._cls_pred_branch,
|
| 277 |
+
gan_ca_blocks=self._gan_ca_blocks,
|
| 278 |
+
concat_time_embeddings=concat_time_embeddings
|
| 279 |
+
)
|
| 280 |
+
flow_pred = flow_pred.permute(0, 2, 1, 3, 4)
|
| 281 |
+
else:
|
| 282 |
+
flow_pred = self.model(
|
| 283 |
+
noisy_image_or_video.permute(0, 2, 1, 3, 4),
|
| 284 |
+
t=input_timestep, context=prompt_embeds,
|
| 285 |
+
seq_len=self.seq_len
|
| 286 |
+
).permute(0, 2, 1, 3, 4)
|
| 287 |
+
|
| 288 |
+
pred_x0 = self._convert_flow_pred_to_x0(
|
| 289 |
+
flow_pred=flow_pred.flatten(0, 1),
|
| 290 |
+
xt=noisy_image_or_video.flatten(0, 1),
|
| 291 |
+
timestep=timestep.flatten(0, 1)
|
| 292 |
+
).unflatten(0, flow_pred.shape[:2])
|
| 293 |
+
|
| 294 |
+
if logits is not None:
|
| 295 |
+
return flow_pred, pred_x0, logits
|
| 296 |
+
|
| 297 |
+
return flow_pred, pred_x0
|
| 298 |
+
|
| 299 |
+
def get_scheduler(self) -> SchedulerInterface:
|
| 300 |
+
"""
|
| 301 |
+
Update the current scheduler with the interface's static method
|
| 302 |
+
"""
|
| 303 |
+
scheduler = self.scheduler
|
| 304 |
+
scheduler.convert_x0_to_noise = types.MethodType(
|
| 305 |
+
SchedulerInterface.convert_x0_to_noise, scheduler)
|
| 306 |
+
scheduler.convert_noise_to_x0 = types.MethodType(
|
| 307 |
+
SchedulerInterface.convert_noise_to_x0, scheduler)
|
| 308 |
+
scheduler.convert_velocity_to_x0 = types.MethodType(
|
| 309 |
+
SchedulerInterface.convert_velocity_to_x0, scheduler)
|
| 310 |
+
self.scheduler = scheduler
|
| 311 |
+
return scheduler
|
| 312 |
+
|
| 313 |
+
def post_init(self):
|
| 314 |
+
"""
|
| 315 |
+
A few custom initialization steps that should be called after the object is created.
|
| 316 |
+
Currently, the only one we have is to bind a few methods to scheduler.
|
| 317 |
+
We can gradually add more methods here if needed.
|
| 318 |
+
"""
|
| 319 |
+
self.get_scheduler()
|