Cccccz commited on
Commit
0226a90
·
verified ·
1 Parent(s): 4d37599

Upload utility source

Browse files
Files changed (7) hide show
  1. utils/dataset.py +220 -0
  2. utils/distributed.py +125 -0
  3. utils/lmdb.py +72 -0
  4. utils/loss.py +81 -0
  5. utils/misc.py +39 -0
  6. utils/scheduler.py +194 -0
  7. 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()