Cccccz commited on
Commit
4d37599
·
verified ·
1 Parent(s): 20962c9

Upload trainer source

Browse files
trainer/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .diffusion import Trainer as DiffusionTrainer
2
+ from .gan import Trainer as GANTrainer
3
+ from .ode import Trainer as ODETrainer
4
+ from .distillation import Trainer as ScoreDistillationTrainer
5
+
6
+ __all__ = [
7
+ "DiffusionTrainer",
8
+ "GANTrainer",
9
+ "ODETrainer",
10
+ "ScoreDistillationTrainer"
11
+ ]
trainer/diffusion.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gc
2
+ import logging
3
+
4
+ from model import CausalDiffusion
5
+ from utils.dataset import ShardingLMDBDataset, cycle
6
+ from utils.misc import set_seed
7
+ import torch.distributed as dist
8
+ from omegaconf import OmegaConf
9
+ import torch
10
+ import wandb
11
+ import time
12
+ import os
13
+
14
+ from utils.distributed import EMA_FSDP, barrier, fsdp_wrap, fsdp_state_dict, launch_distributed_job
15
+
16
+
17
+ class Trainer:
18
+ def __init__(self, config):
19
+ self.config = config
20
+ self.step = 0
21
+
22
+ # Step 1: Initialize the distributed training environment (rank, seed, dtype, logging etc.)
23
+ torch.backends.cuda.matmul.allow_tf32 = True
24
+ torch.backends.cudnn.allow_tf32 = True
25
+
26
+ launch_distributed_job()
27
+ global_rank = dist.get_rank()
28
+
29
+ self.dtype = torch.bfloat16 if config.mixed_precision else torch.float32
30
+ self.device = torch.cuda.current_device()
31
+ self.is_main_process = global_rank == 0
32
+ self.causal = config.causal
33
+ self.disable_wandb = config.disable_wandb
34
+
35
+ # use a random seed for the training
36
+ if config.seed == 0:
37
+ random_seed = torch.randint(0, 10000000, (1,), device=self.device)
38
+ dist.broadcast(random_seed, src=0)
39
+ config.seed = random_seed.item()
40
+
41
+ set_seed(config.seed + global_rank)
42
+
43
+ if self.is_main_process and not self.disable_wandb:
44
+ wandb.login(host=config.wandb_host, key=config.wandb_key)
45
+ wandb.init(
46
+ config=OmegaConf.to_container(config, resolve=True),
47
+ name=config.config_name,
48
+ mode="online",
49
+ entity=config.wandb_entity,
50
+ project=config.wandb_project,
51
+ dir=config.wandb_save_dir
52
+ )
53
+
54
+ self.output_path = config.logdir
55
+
56
+ # Step 2: Initialize the model and optimizer
57
+ self.model = CausalDiffusion(config, device=self.device)
58
+ self.model.generator = fsdp_wrap(
59
+ self.model.generator,
60
+ sharding_strategy=config.sharding_strategy,
61
+ mixed_precision=config.mixed_precision,
62
+ wrap_strategy=config.generator_fsdp_wrap_strategy
63
+ )
64
+
65
+ self.model.text_encoder = fsdp_wrap(
66
+ self.model.text_encoder,
67
+ sharding_strategy=config.sharding_strategy,
68
+ mixed_precision=config.mixed_precision,
69
+ wrap_strategy=config.text_encoder_fsdp_wrap_strategy
70
+ )
71
+
72
+ if not config.no_visualize or config.load_raw_video:
73
+ self.model.vae = self.model.vae.to(
74
+ device=self.device, dtype=torch.bfloat16 if config.mixed_precision else torch.float32)
75
+
76
+ self.generator_optimizer = torch.optim.AdamW(
77
+ [param for param in self.model.generator.parameters()
78
+ if param.requires_grad],
79
+ lr=config.lr,
80
+ betas=(config.beta1, config.beta2),
81
+ weight_decay=config.weight_decay
82
+ )
83
+
84
+ # Step 3: Initialize the dataloader
85
+ dataset = ShardingLMDBDataset(config.data_path, max_pair=int(1e8))
86
+ sampler = torch.utils.data.distributed.DistributedSampler(
87
+ dataset, shuffle=True, drop_last=True)
88
+ dataloader = torch.utils.data.DataLoader(
89
+ dataset,
90
+ batch_size=config.batch_size,
91
+ sampler=sampler,
92
+ num_workers=8)
93
+
94
+ if dist.get_rank() == 0:
95
+ print("DATASET SIZE %d" % len(dataset))
96
+ self.dataloader = cycle(dataloader)
97
+
98
+ ##############################################################################################################
99
+ # 6. Set up EMA parameter containers
100
+ rename_param = (
101
+ lambda name: name.replace("_fsdp_wrapped_module.", "")
102
+ .replace("_checkpoint_wrapped_module.", "")
103
+ .replace("_orig_mod.", "")
104
+ )
105
+ self.name_to_trainable_params = {}
106
+ for n, p in self.model.generator.named_parameters():
107
+ if not p.requires_grad:
108
+ continue
109
+
110
+ renamed_n = rename_param(n)
111
+ self.name_to_trainable_params[renamed_n] = p
112
+ ema_weight = config.ema_weight
113
+ self.generator_ema = None
114
+ if (ema_weight is not None) and (ema_weight > 0.0):
115
+ print(f"Setting up EMA with weight {ema_weight}")
116
+ self.generator_ema = EMA_FSDP(self.model.generator, decay=ema_weight)
117
+
118
+ ##############################################################################################################
119
+ # 7. (If resuming) Load the model and optimizer, lr_scheduler, ema's statedicts
120
+ if getattr(config, "generator_ckpt", False):
121
+ print(f"Loading pretrained generator from {config.generator_ckpt}")
122
+ state_dict = torch.load(config.generator_ckpt, map_location="cpu")
123
+ if "generator" in state_dict:
124
+ state_dict = state_dict["generator"]
125
+ elif "model" in state_dict:
126
+ state_dict = state_dict["model"]
127
+ self.model.generator.load_state_dict(
128
+ state_dict, strict=True
129
+ )
130
+
131
+ ##############################################################################################################
132
+
133
+ # Let's delete EMA params for early steps to save some computes at training and inference
134
+ if self.step < config.ema_start_step:
135
+ self.generator_ema = None
136
+
137
+ self.max_grad_norm = 10.0
138
+ self.previous_time = None
139
+
140
+ def save(self):
141
+ print("Start gathering distributed model states...")
142
+ generator_state_dict = fsdp_state_dict(
143
+ self.model.generator)
144
+
145
+ if self.config.ema_start_step < self.step:
146
+ state_dict = {
147
+ "generator": generator_state_dict,
148
+ "generator_ema": self.generator_ema.state_dict(),
149
+ }
150
+ else:
151
+ state_dict = {
152
+ "generator": generator_state_dict,
153
+ }
154
+
155
+ if self.is_main_process:
156
+ os.makedirs(os.path.join(self.output_path,
157
+ f"checkpoint_model_{self.step:06d}"), exist_ok=True)
158
+ torch.save(state_dict, os.path.join(self.output_path,
159
+ f"checkpoint_model_{self.step:06d}", "model.pt"))
160
+ print("Model saved to", os.path.join(self.output_path,
161
+ f"checkpoint_model_{self.step:06d}", "model.pt"))
162
+
163
+ def train_one_step(self, batch):
164
+ self.log_iters = 1
165
+
166
+ if self.step % 20 == 0:
167
+ torch.cuda.empty_cache()
168
+
169
+ # Step 1: Get the next batch of text prompts
170
+ text_prompts = batch["prompts"]
171
+ if not self.config.load_raw_video: # precomputed latent
172
+ clean_latent = batch["ode_latent"][:, -1].to(
173
+ device=self.device, dtype=self.dtype)
174
+ else: # encode raw video to latent
175
+ frames = batch["frames"].to(
176
+ device=self.device, dtype=self.dtype)
177
+ with torch.no_grad():
178
+ clean_latent = self.model.vae.encode_to_latent(
179
+ frames).to(device=self.device, dtype=self.dtype)
180
+ image_latent = clean_latent[:, 0:1, ]
181
+
182
+ batch_size = len(text_prompts)
183
+ image_or_video_shape = list(self.config.image_or_video_shape)
184
+ image_or_video_shape[0] = batch_size
185
+
186
+ # Step 2: Extract the conditional infos
187
+ with torch.no_grad():
188
+ conditional_dict = self.model.text_encoder(
189
+ text_prompts=text_prompts)
190
+
191
+ if not getattr(self, "unconditional_dict", None):
192
+ unconditional_dict = self.model.text_encoder(
193
+ text_prompts=[self.config.negative_prompt] * batch_size)
194
+ unconditional_dict = {k: v.detach()
195
+ for k, v in unconditional_dict.items()}
196
+ self.unconditional_dict = unconditional_dict # cache the unconditional_dict
197
+ else:
198
+ unconditional_dict = self.unconditional_dict
199
+
200
+ # Step 3: Train the generator
201
+ generator_loss, log_dict = self.model.generator_loss(
202
+ image_or_video_shape=image_or_video_shape,
203
+ conditional_dict=conditional_dict,
204
+ unconditional_dict=unconditional_dict,
205
+ clean_latent=clean_latent,
206
+ initial_latent=image_latent
207
+ )
208
+ self.generator_optimizer.zero_grad()
209
+ generator_loss.backward()
210
+ generator_grad_norm = self.model.generator.clip_grad_norm_(
211
+ self.max_grad_norm)
212
+ self.generator_optimizer.step()
213
+
214
+ # Increment the step since we finished gradient update
215
+ self.step += 1
216
+
217
+ wandb_loss_dict = {
218
+ "generator_loss": generator_loss.item(),
219
+ "generator_grad_norm": generator_grad_norm.item(),
220
+ }
221
+
222
+ # Step 4: Logging
223
+ if self.is_main_process:
224
+ if not self.disable_wandb:
225
+ wandb.log(wandb_loss_dict, step=self.step)
226
+
227
+ if self.step % self.config.gc_interval == 0:
228
+ if dist.get_rank() == 0:
229
+ logging.info("DistGarbageCollector: Running GC.")
230
+ gc.collect()
231
+
232
+ # Step 5. Create EMA params
233
+ # TODO: Implement EMA
234
+
235
+ def generate_video(self, pipeline, prompts, image=None):
236
+ batch_size = len(prompts)
237
+ sampled_noise = torch.randn(
238
+ [batch_size, 21, 16, 60, 104], device="cuda", dtype=self.dtype
239
+ )
240
+ video, _ = pipeline.inference(
241
+ noise=sampled_noise,
242
+ text_prompts=prompts,
243
+ return_latents=True
244
+ )
245
+ current_video = video.permute(0, 1, 3, 4, 2).cpu().numpy() * 255.0
246
+ return current_video
247
+
248
+ def train(self):
249
+ while True:
250
+ batch = next(self.dataloader)
251
+ self.train_one_step(batch)
252
+ if (not self.config.no_save) and self.step % self.config.log_iters == 0:
253
+ torch.cuda.empty_cache()
254
+ self.save()
255
+ torch.cuda.empty_cache()
256
+
257
+ barrier()
258
+ if self.is_main_process:
259
+ current_time = time.time()
260
+ if self.previous_time is None:
261
+ self.previous_time = current_time
262
+ else:
263
+ if not self.disable_wandb:
264
+ wandb.log({"per iteration time": current_time - self.previous_time}, step=self.step)
265
+ self.previous_time = current_time
trainer/distillation.py ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gc
2
+ import logging
3
+
4
+ from utils.dataset import ShardingLMDBDataset, cycle
5
+ from utils.dataset import TextDataset
6
+ from utils.distributed import EMA_FSDP, fsdp_wrap, fsdp_state_dict, launch_distributed_job
7
+ from utils.misc import (
8
+ set_seed,
9
+ merge_dict_list
10
+ )
11
+ import torch.distributed as dist
12
+ from omegaconf import OmegaConf
13
+ from model import CausVid, DMD, SiD
14
+ import torch
15
+ import wandb
16
+ import time
17
+ import os
18
+
19
+
20
+ class Trainer:
21
+ def __init__(self, config):
22
+ self.config = config
23
+ self.step = 0
24
+
25
+ # Step 1: Initialize the distributed training environment (rank, seed, dtype, logging etc.)
26
+ torch.backends.cuda.matmul.allow_tf32 = True
27
+ torch.backends.cudnn.allow_tf32 = True
28
+
29
+ launch_distributed_job()
30
+ global_rank = dist.get_rank()
31
+ self.world_size = dist.get_world_size()
32
+
33
+ self.dtype = torch.bfloat16 if config.mixed_precision else torch.float32
34
+ self.device = torch.cuda.current_device()
35
+ self.is_main_process = global_rank == 0
36
+ self.causal = config.causal
37
+ self.disable_wandb = config.disable_wandb
38
+
39
+ # use a random seed for the training
40
+ if config.seed == 0:
41
+ random_seed = torch.randint(0, 10000000, (1,), device=self.device)
42
+ dist.broadcast(random_seed, src=0)
43
+ config.seed = random_seed.item()
44
+
45
+ set_seed(config.seed + global_rank)
46
+
47
+ if self.is_main_process and not self.disable_wandb:
48
+ wandb.login(host=config.wandb_host, key=config.wandb_key)
49
+ wandb.init(
50
+ config=OmegaConf.to_container(config, resolve=True),
51
+ name=config.config_name,
52
+ mode="online",
53
+ entity=config.wandb_entity,
54
+ project=config.wandb_project,
55
+ dir=config.wandb_save_dir
56
+ )
57
+
58
+ self.output_path = config.logdir
59
+
60
+ # Step 2: Initialize the model and optimizer
61
+ if config.distribution_loss == "causvid":
62
+ self.model = CausVid(config, device=self.device)
63
+ elif config.distribution_loss == "dmd":
64
+ self.model = DMD(config, device=self.device)
65
+ elif config.distribution_loss == "sid":
66
+ self.model = SiD(config, device=self.device)
67
+ else:
68
+ raise ValueError("Invalid distribution matching loss")
69
+
70
+ # Save pretrained model state_dicts to CPU
71
+ self.fake_score_state_dict_cpu = self.model.fake_score.state_dict()
72
+
73
+ self.model.generator = fsdp_wrap(
74
+ self.model.generator,
75
+ sharding_strategy=config.sharding_strategy,
76
+ mixed_precision=config.mixed_precision,
77
+ wrap_strategy=config.generator_fsdp_wrap_strategy
78
+ )
79
+
80
+ self.model.real_score = fsdp_wrap(
81
+ self.model.real_score,
82
+ sharding_strategy=config.sharding_strategy,
83
+ mixed_precision=config.mixed_precision,
84
+ wrap_strategy=config.real_score_fsdp_wrap_strategy
85
+ )
86
+
87
+ self.model.fake_score = fsdp_wrap(
88
+ self.model.fake_score,
89
+ sharding_strategy=config.sharding_strategy,
90
+ mixed_precision=config.mixed_precision,
91
+ wrap_strategy=config.fake_score_fsdp_wrap_strategy
92
+ )
93
+
94
+ self.model.text_encoder = fsdp_wrap(
95
+ self.model.text_encoder,
96
+ sharding_strategy=config.sharding_strategy,
97
+ mixed_precision=config.mixed_precision,
98
+ wrap_strategy=config.text_encoder_fsdp_wrap_strategy,
99
+ cpu_offload=getattr(config, "text_encoder_cpu_offload", False)
100
+ )
101
+
102
+ if not config.no_visualize or config.load_raw_video:
103
+ self.model.vae = self.model.vae.to(
104
+ device=self.device, dtype=torch.bfloat16 if config.mixed_precision else torch.float32)
105
+
106
+ self.generator_optimizer = torch.optim.AdamW(
107
+ [param for param in self.model.generator.parameters()
108
+ if param.requires_grad],
109
+ lr=config.lr,
110
+ betas=(config.beta1, config.beta2),
111
+ weight_decay=config.weight_decay
112
+ )
113
+
114
+ self.critic_optimizer = torch.optim.AdamW(
115
+ [param for param in self.model.fake_score.parameters()
116
+ if param.requires_grad],
117
+ lr=config.lr_critic if hasattr(config, "lr_critic") else config.lr,
118
+ betas=(config.beta1_critic, config.beta2_critic),
119
+ weight_decay=config.weight_decay
120
+ )
121
+
122
+ # Step 3: Initialize the dataloader
123
+ if self.config.i2v:
124
+ dataset = ShardingLMDBDataset(config.data_path, max_pair=int(1e8))
125
+ else:
126
+ dataset = TextDataset(config.data_path)
127
+ sampler = torch.utils.data.distributed.DistributedSampler(
128
+ dataset, shuffle=True, drop_last=True)
129
+ dataloader = torch.utils.data.DataLoader(
130
+ dataset,
131
+ batch_size=config.batch_size,
132
+ sampler=sampler,
133
+ num_workers=8)
134
+
135
+ if dist.get_rank() == 0:
136
+ print("DATASET SIZE %d" % len(dataset))
137
+ self.dataloader = cycle(dataloader)
138
+
139
+ ##############################################################################################################
140
+ # 6. Set up EMA parameter containers
141
+ rename_param = (
142
+ lambda name: name.replace("_fsdp_wrapped_module.", "")
143
+ .replace("_checkpoint_wrapped_module.", "")
144
+ .replace("_orig_mod.", "")
145
+ )
146
+ self.name_to_trainable_params = {}
147
+ for n, p in self.model.generator.named_parameters():
148
+ if not p.requires_grad:
149
+ continue
150
+
151
+ renamed_n = rename_param(n)
152
+ self.name_to_trainable_params[renamed_n] = p
153
+ ema_weight = config.ema_weight
154
+ self.generator_ema = None
155
+ if (ema_weight is not None) and (ema_weight > 0.0):
156
+ print(f"Setting up EMA with weight {ema_weight}")
157
+ self.generator_ema = EMA_FSDP(self.model.generator, decay=ema_weight)
158
+
159
+ ##############################################################################################################
160
+ # 7. (If resuming) Load the model and optimizer, lr_scheduler, ema's statedicts
161
+ if getattr(config, "generator_ckpt", False):
162
+ print(f"Loading pretrained generator from {config.generator_ckpt}")
163
+ state_dict = torch.load(config.generator_ckpt, map_location="cpu")
164
+ if "generator" in state_dict:
165
+ state_dict = state_dict["generator"]
166
+ elif "model" in state_dict:
167
+ state_dict = state_dict["model"]
168
+ self.model.generator.load_state_dict(
169
+ state_dict, strict=True
170
+ )
171
+
172
+ ##############################################################################################################
173
+
174
+ # Let's delete EMA params for early steps to save some computes at training and inference
175
+ if self.step < config.ema_start_step:
176
+ self.generator_ema = None
177
+
178
+ self.max_grad_norm_generator = getattr(config, "max_grad_norm_generator", 10.0)
179
+ self.max_grad_norm_critic = getattr(config, "max_grad_norm_critic", 10.0)
180
+ self.previous_time = None
181
+
182
+ def save(self):
183
+ print("Start gathering distributed model states...")
184
+ generator_state_dict = fsdp_state_dict(
185
+ self.model.generator)
186
+ critic_state_dict = fsdp_state_dict(
187
+ self.model.fake_score)
188
+
189
+ if self.config.ema_start_step < self.step:
190
+ state_dict = {
191
+ "generator": generator_state_dict,
192
+ "critic": critic_state_dict,
193
+ "generator_ema": self.generator_ema.state_dict(),
194
+ }
195
+ else:
196
+ state_dict = {
197
+ "generator": generator_state_dict,
198
+ "critic": critic_state_dict,
199
+ }
200
+
201
+ if self.is_main_process:
202
+ os.makedirs(os.path.join(self.output_path,
203
+ f"checkpoint_model_{self.step:06d}"), exist_ok=True)
204
+ torch.save(state_dict, os.path.join(self.output_path,
205
+ f"checkpoint_model_{self.step:06d}", "model.pt"))
206
+ print("Model saved to", os.path.join(self.output_path,
207
+ f"checkpoint_model_{self.step:06d}", "model.pt"))
208
+
209
+ def fwdbwd_one_step(self, batch, train_generator):
210
+ self.model.eval() # prevent any randomness (e.g. dropout)
211
+
212
+ if self.step % 20 == 0:
213
+ torch.cuda.empty_cache()
214
+
215
+ # Step 1: Get the next batch of text prompts
216
+ text_prompts = batch["prompts"]
217
+ if self.config.i2v:
218
+ clean_latent = None
219
+ image_latent = batch["ode_latent"][:, -1][:, 0:1, ].to(
220
+ device=self.device, dtype=self.dtype)
221
+ else:
222
+ clean_latent = None
223
+ image_latent = None
224
+
225
+ batch_size = len(text_prompts)
226
+ image_or_video_shape = list(self.config.image_or_video_shape)
227
+ image_or_video_shape[0] = batch_size
228
+
229
+ # Step 2: Extract the conditional infos
230
+ with torch.no_grad():
231
+ conditional_dict = self.model.text_encoder(
232
+ text_prompts=text_prompts)
233
+
234
+ if not getattr(self, "unconditional_dict", None):
235
+ unconditional_dict = self.model.text_encoder(
236
+ text_prompts=[self.config.negative_prompt] * batch_size)
237
+ unconditional_dict = {k: v.detach()
238
+ for k, v in unconditional_dict.items()}
239
+ self.unconditional_dict = unconditional_dict # cache the unconditional_dict
240
+ else:
241
+ unconditional_dict = self.unconditional_dict
242
+
243
+ # Step 3: Store gradients for the generator (if training the generator)
244
+ if train_generator:
245
+ generator_loss, generator_log_dict = self.model.generator_loss(
246
+ image_or_video_shape=image_or_video_shape,
247
+ conditional_dict=conditional_dict,
248
+ unconditional_dict=unconditional_dict,
249
+ clean_latent=clean_latent,
250
+ initial_latent=image_latent if self.config.i2v else None
251
+ )
252
+
253
+ generator_loss.backward()
254
+ generator_grad_norm = self.model.generator.clip_grad_norm_(
255
+ self.max_grad_norm_generator)
256
+
257
+ generator_log_dict.update({"generator_loss": generator_loss,
258
+ "generator_grad_norm": generator_grad_norm})
259
+
260
+ return generator_log_dict
261
+ else:
262
+ generator_log_dict = {}
263
+
264
+ # Step 4: Store gradients for the critic (if training the critic)
265
+ critic_loss, critic_log_dict = self.model.critic_loss(
266
+ image_or_video_shape=image_or_video_shape,
267
+ conditional_dict=conditional_dict,
268
+ unconditional_dict=unconditional_dict,
269
+ clean_latent=clean_latent,
270
+ initial_latent=image_latent if self.config.i2v else None
271
+ )
272
+
273
+ critic_loss.backward()
274
+ critic_grad_norm = self.model.fake_score.clip_grad_norm_(
275
+ self.max_grad_norm_critic)
276
+
277
+ critic_log_dict.update({"critic_loss": critic_loss,
278
+ "critic_grad_norm": critic_grad_norm})
279
+
280
+ return critic_log_dict
281
+
282
+ def generate_video(self, pipeline, prompts, image=None):
283
+ batch_size = len(prompts)
284
+ if image is not None:
285
+ image = image.squeeze(0).unsqueeze(0).unsqueeze(2).to(device="cuda", dtype=torch.bfloat16)
286
+
287
+ # Encode the input image as the first latent
288
+ initial_latent = pipeline.vae.encode_to_latent(image).to(device="cuda", dtype=torch.bfloat16)
289
+ initial_latent = initial_latent.repeat(batch_size, 1, 1, 1, 1)
290
+ sampled_noise = torch.randn(
291
+ [batch_size, self.model.num_training_frames - 1, 16, 60, 104],
292
+ device="cuda",
293
+ dtype=self.dtype
294
+ )
295
+ else:
296
+ initial_latent = None
297
+ sampled_noise = torch.randn(
298
+ [batch_size, self.model.num_training_frames, 16, 60, 104],
299
+ device="cuda",
300
+ dtype=self.dtype
301
+ )
302
+
303
+ video, _ = pipeline.inference(
304
+ noise=sampled_noise,
305
+ text_prompts=prompts,
306
+ return_latents=True,
307
+ initial_latent=initial_latent
308
+ )
309
+ current_video = video.permute(0, 1, 3, 4, 2).cpu().numpy() * 255.0
310
+ return current_video
311
+
312
+ def train(self):
313
+ start_step = self.step
314
+
315
+ while True:
316
+ TRAIN_GENERATOR = self.step % self.config.dfake_gen_update_ratio == 0
317
+
318
+ # Train the generator
319
+ if TRAIN_GENERATOR:
320
+ self.generator_optimizer.zero_grad(set_to_none=True)
321
+ extras_list = []
322
+ batch = next(self.dataloader)
323
+ extra = self.fwdbwd_one_step(batch, True)
324
+ extras_list.append(extra)
325
+ generator_log_dict = merge_dict_list(extras_list)
326
+ self.generator_optimizer.step()
327
+ if self.generator_ema is not None:
328
+ self.generator_ema.update(self.model.generator)
329
+
330
+ # Train the critic
331
+ self.critic_optimizer.zero_grad(set_to_none=True)
332
+ extras_list = []
333
+ batch = next(self.dataloader)
334
+ extra = self.fwdbwd_one_step(batch, False)
335
+ extras_list.append(extra)
336
+ critic_log_dict = merge_dict_list(extras_list)
337
+ self.critic_optimizer.step()
338
+
339
+ # Increment the step since we finished gradient update
340
+ self.step += 1
341
+
342
+ # Create EMA params (if not already created)
343
+ if (self.step >= self.config.ema_start_step) and \
344
+ (self.generator_ema is None) and (self.config.ema_weight > 0):
345
+ self.generator_ema = EMA_FSDP(self.model.generator, decay=self.config.ema_weight)
346
+
347
+ # Save the model
348
+ if (not self.config.no_save) and (self.step - start_step) > 0 and self.step % self.config.log_iters == 0:
349
+ torch.cuda.empty_cache()
350
+ self.save()
351
+ torch.cuda.empty_cache()
352
+
353
+ # Logging
354
+ if self.is_main_process:
355
+ wandb_loss_dict = {}
356
+ if TRAIN_GENERATOR:
357
+ wandb_loss_dict.update(
358
+ {
359
+ "generator_loss": generator_log_dict["generator_loss"].mean().item(),
360
+ "generator_grad_norm": generator_log_dict["generator_grad_norm"].mean().item(),
361
+ "dmdtrain_gradient_norm": generator_log_dict["dmdtrain_gradient_norm"].mean().item()
362
+ }
363
+ )
364
+
365
+ wandb_loss_dict.update(
366
+ {
367
+ "critic_loss": critic_log_dict["critic_loss"].mean().item(),
368
+ "critic_grad_norm": critic_log_dict["critic_grad_norm"].mean().item()
369
+ }
370
+ )
371
+
372
+ if not self.disable_wandb:
373
+ wandb.log(wandb_loss_dict, step=self.step)
374
+
375
+ if self.step % self.config.gc_interval == 0:
376
+ if dist.get_rank() == 0:
377
+ logging.info("DistGarbageCollector: Running GC.")
378
+ gc.collect()
379
+ torch.cuda.empty_cache()
380
+
381
+ if self.is_main_process:
382
+ current_time = time.time()
383
+ if self.previous_time is None:
384
+ self.previous_time = current_time
385
+ else:
386
+ if not self.disable_wandb:
387
+ wandb.log({"per iteration time": current_time - self.previous_time}, step=self.step)
388
+ self.previous_time = current_time
trainer/gan.py ADDED
@@ -0,0 +1,464 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gc
2
+ import logging
3
+
4
+ from utils.dataset import ShardingLMDBDataset, cycle
5
+ from utils.distributed import EMA_FSDP, fsdp_wrap, fsdp_state_dict, launch_distributed_job
6
+ from utils.misc import (
7
+ set_seed,
8
+ merge_dict_list
9
+ )
10
+ import torch.distributed as dist
11
+ from omegaconf import OmegaConf
12
+ from model import GAN
13
+ import torch
14
+ import wandb
15
+ import time
16
+ import os
17
+
18
+
19
+ class Trainer:
20
+ def __init__(self, config):
21
+ self.config = config
22
+ self.step = 0
23
+
24
+ # Step 1: Initialize the distributed training environment (rank, seed, dtype, logging etc.)
25
+ torch.backends.cuda.matmul.allow_tf32 = True
26
+ torch.backends.cudnn.allow_tf32 = True
27
+
28
+ launch_distributed_job()
29
+ global_rank = dist.get_rank()
30
+ self.world_size = dist.get_world_size()
31
+
32
+ self.dtype = torch.bfloat16 if config.mixed_precision else torch.float32
33
+ self.device = torch.cuda.current_device()
34
+ self.is_main_process = global_rank == 0
35
+ self.causal = config.causal
36
+ self.disable_wandb = config.disable_wandb
37
+
38
+ # Configuration for discriminator warmup
39
+ self.discriminator_warmup_steps = getattr(config, "discriminator_warmup_steps", 0)
40
+ self.in_discriminator_warmup = self.step < self.discriminator_warmup_steps
41
+ if self.in_discriminator_warmup and self.is_main_process:
42
+ print(f"Starting with discriminator warmup for {self.discriminator_warmup_steps} steps")
43
+ self.loss_scale = getattr(config, "loss_scale", 1.0)
44
+
45
+ # use a random seed for the training
46
+ if config.seed == 0:
47
+ random_seed = torch.randint(0, 10000000, (1,), device=self.device)
48
+ dist.broadcast(random_seed, src=0)
49
+ config.seed = random_seed.item()
50
+
51
+ set_seed(config.seed + global_rank)
52
+
53
+ if self.is_main_process and not self.disable_wandb:
54
+ wandb.login(host=config.wandb_host, key=config.wandb_key)
55
+ wandb.init(
56
+ config=OmegaConf.to_container(config, resolve=True),
57
+ name=config.config_name,
58
+ mode="online",
59
+ entity=config.wandb_entity,
60
+ project=config.wandb_project,
61
+ dir=config.wandb_save_dir
62
+ )
63
+
64
+ self.output_path = config.logdir
65
+
66
+ # Step 2: Initialize the model and optimizer
67
+ self.model = GAN(config, device=self.device)
68
+
69
+ self.model.generator = fsdp_wrap(
70
+ self.model.generator,
71
+ sharding_strategy=config.sharding_strategy,
72
+ mixed_precision=config.mixed_precision,
73
+ wrap_strategy=config.generator_fsdp_wrap_strategy
74
+ )
75
+
76
+ self.model.fake_score = fsdp_wrap(
77
+ self.model.fake_score,
78
+ sharding_strategy=config.sharding_strategy,
79
+ mixed_precision=config.mixed_precision,
80
+ wrap_strategy=config.fake_score_fsdp_wrap_strategy
81
+ )
82
+
83
+ self.model.text_encoder = fsdp_wrap(
84
+ self.model.text_encoder,
85
+ sharding_strategy=config.sharding_strategy,
86
+ mixed_precision=config.mixed_precision,
87
+ wrap_strategy=config.text_encoder_fsdp_wrap_strategy,
88
+ cpu_offload=getattr(config, "text_encoder_cpu_offload", False)
89
+ )
90
+
91
+ if not config.no_visualize or config.load_raw_video:
92
+ self.model.vae = self.model.vae.to(
93
+ device=self.device, dtype=torch.bfloat16 if config.mixed_precision else torch.float32)
94
+
95
+ self.generator_optimizer = torch.optim.AdamW(
96
+ [param for param in self.model.generator.parameters()
97
+ if param.requires_grad],
98
+ lr=config.gen_lr,
99
+ betas=(config.beta1, config.beta2)
100
+ )
101
+
102
+ # Create separate parameter groups for the fake_score network
103
+ # One group for parameters with "_cls_pred_branch" or "_gan_ca_blocks" in the name
104
+ # and another group for all other parameters
105
+ fake_score_params = []
106
+ discriminator_params = []
107
+
108
+ for name, param in self.model.fake_score.named_parameters():
109
+ if param.requires_grad:
110
+ if "_cls_pred_branch" in name or "_gan_ca_blocks" in name:
111
+ discriminator_params.append(param)
112
+ else:
113
+ fake_score_params.append(param)
114
+
115
+ # Use the special learning rate for the special parameter group
116
+ # and the default critic learning rate for other parameters
117
+ self.critic_param_groups = [
118
+ {'params': fake_score_params, 'lr': config.critic_lr},
119
+ {'params': discriminator_params, 'lr': config.critic_lr * config.discriminator_lr_multiplier}
120
+ ]
121
+ if self.in_discriminator_warmup:
122
+ self.critic_optimizer = torch.optim.AdamW(
123
+ self.critic_param_groups,
124
+ betas=(0.9, config.beta2_critic)
125
+ )
126
+ else:
127
+ self.critic_optimizer = torch.optim.AdamW(
128
+ self.critic_param_groups,
129
+ betas=(config.beta1_critic, config.beta2_critic)
130
+ )
131
+
132
+ # Step 3: Initialize the dataloader
133
+ self.data_path = config.data_path
134
+ dataset = ShardingLMDBDataset(config.data_path, max_pair=int(1e8))
135
+ sampler = torch.utils.data.distributed.DistributedSampler(
136
+ dataset, shuffle=True, drop_last=True)
137
+ dataloader = torch.utils.data.DataLoader(
138
+ dataset,
139
+ batch_size=config.batch_size,
140
+ sampler=sampler,
141
+ num_workers=8)
142
+
143
+ if dist.get_rank() == 0:
144
+ print("DATASET SIZE %d" % len(dataset))
145
+
146
+ self.dataloader = cycle(dataloader)
147
+
148
+ ##############################################################################################################
149
+ # 6. Set up EMA parameter containers
150
+ rename_param = (
151
+ lambda name: name.replace("_fsdp_wrapped_module.", "")
152
+ .replace("_checkpoint_wrapped_module.", "")
153
+ .replace("_orig_mod.", "")
154
+ )
155
+ self.name_to_trainable_params = {}
156
+ for n, p in self.model.generator.named_parameters():
157
+ if not p.requires_grad:
158
+ continue
159
+
160
+ renamed_n = rename_param(n)
161
+ self.name_to_trainable_params[renamed_n] = p
162
+ ema_weight = config.ema_weight
163
+ self.generator_ema = None
164
+ if (ema_weight is not None) and (ema_weight > 0.0):
165
+ print(f"Setting up EMA with weight {ema_weight}")
166
+ self.generator_ema = EMA_FSDP(self.model.generator, decay=ema_weight)
167
+
168
+ ##############################################################################################################
169
+ # 7. (If resuming) Load the model and optimizer, lr_scheduler, ema's statedicts
170
+ if getattr(config, "generator_ckpt", False):
171
+ print(f"Loading pretrained generator from {config.generator_ckpt}")
172
+ state_dict = torch.load(config.generator_ckpt, map_location="cpu")
173
+ if "generator" in state_dict:
174
+ state_dict = state_dict["generator"]
175
+ elif "model" in state_dict:
176
+ state_dict = state_dict["model"]
177
+ self.model.generator.load_state_dict(
178
+ state_dict, strict=True
179
+ )
180
+ if hasattr(config, "load"):
181
+ resume_ckpt_path_critic = os.path.join(config.load, "critic")
182
+ resume_ckpt_path_generator = os.path.join(config.load, "generator")
183
+ else:
184
+ resume_ckpt_path_critic = "none"
185
+ resume_ckpt_path_generator = "none"
186
+
187
+ _, _ = self.checkpointer_critic.try_best_load(
188
+ resume_ckpt_path=resume_ckpt_path_critic,
189
+ )
190
+ self.step, _ = self.checkpointer_generator.try_best_load(
191
+ resume_ckpt_path=resume_ckpt_path_generator,
192
+ force_start_w_ema=config.force_start_w_ema,
193
+ force_reset_zero_step=config.force_reset_zero_step,
194
+ force_reinit_ema=config.force_reinit_ema,
195
+ skip_optimizer_scheduler=config.skip_optimizer_scheduler,
196
+ )
197
+
198
+ ##############################################################################################################
199
+
200
+ # Let's delete EMA params for early steps to save some computes at training and inference
201
+ if self.step < config.ema_start_step:
202
+ self.generator_ema = None
203
+
204
+ self.max_grad_norm_generator = getattr(config, "max_grad_norm_generator", 10.0)
205
+ self.max_grad_norm_critic = getattr(config, "max_grad_norm_critic", 10.0)
206
+ self.previous_time = None
207
+
208
+ def save(self):
209
+ print("Start gathering distributed model states...")
210
+ generator_state_dict = fsdp_state_dict(
211
+ self.model.generator)
212
+ critic_state_dict = fsdp_state_dict(
213
+ self.model.fake_score)
214
+
215
+ if self.config.ema_start_step < self.step:
216
+ state_dict = {
217
+ "generator": generator_state_dict,
218
+ "critic": critic_state_dict,
219
+ "generator_ema": self.generator_ema.state_dict(),
220
+ }
221
+ else:
222
+ state_dict = {
223
+ "generator": generator_state_dict,
224
+ "critic": critic_state_dict,
225
+ }
226
+
227
+ if self.is_main_process:
228
+ os.makedirs(os.path.join(self.output_path,
229
+ f"checkpoint_model_{self.step:06d}"), exist_ok=True)
230
+ torch.save(state_dict, os.path.join(self.output_path,
231
+ f"checkpoint_model_{self.step:06d}", "model.pt"))
232
+ print("Model saved to", os.path.join(self.output_path,
233
+ f"checkpoint_model_{self.step:06d}", "model.pt"))
234
+
235
+ def fwdbwd_one_step(self, batch, train_generator):
236
+ self.model.eval() # prevent any randomness (e.g. dropout)
237
+
238
+ if self.step % 20 == 0:
239
+ torch.cuda.empty_cache()
240
+
241
+ # Step 1: Get the next batch of text prompts
242
+ text_prompts = batch["prompts"] # next(self.dataloader)
243
+ if "ode_latent" in batch:
244
+ clean_latent = batch["ode_latent"][:, -1].to(device=self.device, dtype=self.dtype)
245
+ else:
246
+ frames = batch["frames"].to(device=self.device, dtype=self.dtype)
247
+ with torch.no_grad():
248
+ clean_latent = self.model.vae.encode_to_latent(
249
+ frames).to(device=self.device, dtype=self.dtype)
250
+
251
+ image_latent = clean_latent[:, 0:1, ]
252
+
253
+ batch_size = len(text_prompts)
254
+ image_or_video_shape = list(self.config.image_or_video_shape)
255
+ image_or_video_shape[0] = batch_size
256
+
257
+ # Step 2: Extract the conditional infos
258
+ with torch.no_grad():
259
+ conditional_dict = self.model.text_encoder(
260
+ text_prompts=text_prompts)
261
+
262
+ if not getattr(self, "unconditional_dict", None):
263
+ unconditional_dict = self.model.text_encoder(
264
+ text_prompts=[self.config.negative_prompt] * batch_size)
265
+ unconditional_dict = {k: v.detach()
266
+ for k, v in unconditional_dict.items()}
267
+ self.unconditional_dict = unconditional_dict # cache the unconditional_dict
268
+ else:
269
+ unconditional_dict = self.unconditional_dict
270
+
271
+ mini_bs, full_bs = (
272
+ batch["mini_bs"],
273
+ batch["full_bs"],
274
+ )
275
+
276
+ # Step 3: Store gradients for the generator (if training the generator)
277
+ if train_generator:
278
+ gan_G_loss = self.model.generator_loss(
279
+ image_or_video_shape=image_or_video_shape,
280
+ conditional_dict=conditional_dict,
281
+ unconditional_dict=unconditional_dict,
282
+ clean_latent=clean_latent,
283
+ initial_latent=image_latent if self.config.i2v else None
284
+ )
285
+
286
+ loss_ratio = mini_bs * self.world_size / full_bs
287
+ total_loss = gan_G_loss * loss_ratio * self.loss_scale
288
+
289
+ total_loss.backward()
290
+ generator_grad_norm = self.model.generator.clip_grad_norm_(
291
+ self.max_grad_norm_generator)
292
+
293
+ generator_log_dict = {"generator_grad_norm": generator_grad_norm,
294
+ "gan_G_loss": gan_G_loss}
295
+
296
+ return generator_log_dict
297
+ else:
298
+ generator_log_dict = {}
299
+
300
+ # Step 4: Store gradients for the critic (if training the critic)
301
+ (gan_D_loss, r1_loss, r2_loss), critic_log_dict = self.model.critic_loss(
302
+ image_or_video_shape=image_or_video_shape,
303
+ conditional_dict=conditional_dict,
304
+ unconditional_dict=unconditional_dict,
305
+ clean_latent=clean_latent,
306
+ real_image_or_video=clean_latent,
307
+ initial_latent=image_latent if self.config.i2v else None
308
+ )
309
+
310
+ loss_ratio = mini_bs * dist.get_world_size() / full_bs
311
+ total_loss = (gan_D_loss + 0.5 * (r1_loss + r2_loss)) * loss_ratio * self.loss_scale
312
+
313
+ total_loss.backward()
314
+ critic_grad_norm = self.model.fake_score.clip_grad_norm_(
315
+ self.max_grad_norm_critic)
316
+
317
+ critic_log_dict.update({"critic_grad_norm": critic_grad_norm,
318
+ "gan_D_loss": gan_D_loss,
319
+ "r1_loss": r1_loss,
320
+ "r2_loss": r2_loss})
321
+
322
+ return critic_log_dict
323
+
324
+ def generate_video(self, pipeline, prompts, image=None):
325
+ batch_size = len(prompts)
326
+ sampled_noise = torch.randn(
327
+ [batch_size, 21, 16, 60, 104], device="cuda", dtype=self.dtype
328
+ )
329
+ video, _ = pipeline.inference(
330
+ noise=sampled_noise,
331
+ text_prompts=prompts,
332
+ return_latents=True
333
+ )
334
+ current_video = video.permute(0, 1, 3, 4, 2).cpu().numpy() * 255.0
335
+ return current_video
336
+
337
+ def train(self):
338
+ start_step = self.step
339
+
340
+ while True:
341
+ if self.step == self.discriminator_warmup_steps and self.discriminator_warmup_steps != 0:
342
+ print("Resetting critic optimizer")
343
+ del self.critic_optimizer
344
+ torch.cuda.empty_cache()
345
+ # Create new optimizers
346
+ self.critic_optimizer = torch.optim.AdamW(
347
+ self.critic_param_groups,
348
+ betas=(self.config.beta1_critic, self.config.beta2_critic)
349
+ )
350
+ # Update checkpointer references
351
+ self.checkpointer_critic.optimizer = self.critic_optimizer
352
+ # Check if we're in the discriminator warmup phase
353
+ self.in_discriminator_warmup = self.step < self.discriminator_warmup_steps
354
+
355
+ # Only update generator and critic outside the warmup phase
356
+ TRAIN_GENERATOR = not self.in_discriminator_warmup and self.step % self.config.dfake_gen_update_ratio == 0
357
+
358
+ # Train the generator (only outside warmup phase)
359
+ if TRAIN_GENERATOR:
360
+ self.model.fake_score.requires_grad_(False)
361
+ self.model.generator.requires_grad_(True)
362
+ self.generator_optimizer.zero_grad(set_to_none=True)
363
+ extras_list = []
364
+ for ii, mini_batch in enumerate(self.dataloader.next()):
365
+ extra = self.fwdbwd_one_step(mini_batch, True)
366
+ extras_list.append(extra)
367
+ generator_log_dict = merge_dict_list(extras_list)
368
+ self.generator_optimizer.step()
369
+ if self.generator_ema is not None:
370
+ self.generator_ema.update(self.model.generator)
371
+ else:
372
+ generator_log_dict = {}
373
+
374
+ # Train the critic/discriminator
375
+ if self.in_discriminator_warmup:
376
+ # During warmup, only allow gradient for discriminator params
377
+ self.model.generator.requires_grad_(False)
378
+ self.model.fake_score.requires_grad_(False)
379
+
380
+ # Enable gradient only for discriminator params
381
+ for name, param in self.model.fake_score.named_parameters():
382
+ if "_cls_pred_branch" in name or "_gan_ca_blocks" in name:
383
+ param.requires_grad_(True)
384
+ else:
385
+ # Normal training mode
386
+ self.model.generator.requires_grad_(False)
387
+ self.model.fake_score.requires_grad_(True)
388
+
389
+ self.critic_optimizer.zero_grad(set_to_none=True)
390
+ extras_list = []
391
+ batch = next(self.dataloader)
392
+ extra = self.fwdbwd_one_step(batch, False)
393
+ extras_list.append(extra)
394
+ critic_log_dict = merge_dict_list(extras_list)
395
+ self.critic_optimizer.step()
396
+
397
+ # Increment the step since we finished gradient update
398
+ self.step += 1
399
+
400
+ # If we just finished warmup, print a message
401
+ if self.is_main_process and self.step == self.discriminator_warmup_steps:
402
+ print(f"Finished discriminator warmup after {self.discriminator_warmup_steps} steps")
403
+
404
+ # Create EMA params (if not already created)
405
+ if (self.step >= self.config.ema_start_step) and \
406
+ (self.generator_ema is None) and (self.config.ema_weight > 0):
407
+ self.generator_ema = EMA_FSDP(self.model.generator, decay=self.config.ema_weight)
408
+
409
+ # Save the model
410
+ if (not self.config.no_save) and (self.step - start_step) > 0 and self.step % self.config.log_iters == 0:
411
+ torch.cuda.empty_cache()
412
+ self.save()
413
+ torch.cuda.empty_cache()
414
+
415
+ # Logging
416
+ wandb_loss_dict = {
417
+ "generator_grad_norm": generator_log_dict["generator_grad_norm"],
418
+ "critic_grad_norm": critic_log_dict["critic_grad_norm"],
419
+ "real_logit": critic_log_dict["noisy_real_logit"],
420
+ "fake_logit": critic_log_dict["noisy_fake_logit"],
421
+ "r1_loss": critic_log_dict["r1_loss"],
422
+ "r2_loss": critic_log_dict["r2_loss"],
423
+ }
424
+ if TRAIN_GENERATOR:
425
+ wandb_loss_dict.update({
426
+ "generator_grad_norm": generator_log_dict["generator_grad_norm"],
427
+ })
428
+ self.all_gather_dict(wandb_loss_dict)
429
+ wandb_loss_dict["diff_logit"] = wandb_loss_dict["real_logit"] - wandb_loss_dict["fake_logit"]
430
+ wandb_loss_dict["reg_loss"] = 0.5 * (wandb_loss_dict["r1_loss"] + wandb_loss_dict["r2_loss"])
431
+
432
+ if self.is_main_process:
433
+ if self.in_discriminator_warmup:
434
+ warmup_status = f"[WARMUP {self.step}/{self.discriminator_warmup_steps}] Training only discriminator params"
435
+ print(warmup_status)
436
+ if not self.disable_wandb:
437
+ wandb_loss_dict.update({"warmup_status": 1.0})
438
+
439
+ if not self.disable_wandb:
440
+ wandb.log(wandb_loss_dict, step=self.step)
441
+
442
+ if self.step % self.config.gc_interval == 0:
443
+ if dist.get_rank() == 0:
444
+ logging.info("DistGarbageCollector: Running GC.")
445
+ gc.collect()
446
+ torch.cuda.empty_cache()
447
+
448
+ if self.is_main_process:
449
+ current_time = time.time()
450
+ if self.previous_time is None:
451
+ self.previous_time = current_time
452
+ else:
453
+ if not self.disable_wandb:
454
+ wandb.log({"per iteration time": current_time - self.previous_time}, step=self.step)
455
+ self.previous_time = current_time
456
+
457
+ def all_gather_dict(self, target_dict):
458
+ for key, value in target_dict.items():
459
+ gathered_value = torch.zeros(
460
+ [self.world_size, *value.shape],
461
+ dtype=value.dtype, device=self.device)
462
+ dist.all_gather_into_tensor(gathered_value, value)
463
+ avg_value = gathered_value.mean().item()
464
+ target_dict[key] = avg_value
trainer/ode.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gc
2
+ import logging
3
+ from utils.dataset import ODERegressionLMDBDataset, cycle
4
+ from model import ODERegression
5
+ from collections import defaultdict
6
+ from utils.misc import (
7
+ set_seed
8
+ )
9
+ import torch.distributed as dist
10
+ from omegaconf import OmegaConf
11
+ import torch
12
+ import wandb
13
+ import time
14
+ import os
15
+
16
+ from utils.distributed import barrier, fsdp_wrap, fsdp_state_dict, launch_distributed_job
17
+
18
+
19
+ class Trainer:
20
+ def __init__(self, config):
21
+ self.config = config
22
+ self.step = 0
23
+
24
+ # Step 1: Initialize the distributed training environment (rank, seed, dtype, logging etc.)
25
+ torch.backends.cuda.matmul.allow_tf32 = True
26
+ torch.backends.cudnn.allow_tf32 = True
27
+
28
+ launch_distributed_job()
29
+ global_rank = dist.get_rank()
30
+ self.world_size = dist.get_world_size()
31
+
32
+ self.dtype = torch.bfloat16 if config.mixed_precision else torch.float32
33
+ self.device = torch.cuda.current_device()
34
+ self.is_main_process = global_rank == 0
35
+ self.disable_wandb = config.disable_wandb
36
+
37
+ # use a random seed for the training
38
+ if config.seed == 0:
39
+ random_seed = torch.randint(0, 10000000, (1,), device=self.device)
40
+ dist.broadcast(random_seed, src=0)
41
+ config.seed = random_seed.item()
42
+
43
+ set_seed(config.seed + global_rank)
44
+
45
+ if self.is_main_process and not self.disable_wandb:
46
+ wandb.login(host=config.wandb_host, key=config.wandb_key)
47
+ wandb.init(
48
+ config=OmegaConf.to_container(config, resolve=True),
49
+ name=config.config_name,
50
+ mode="online",
51
+ entity=config.wandb_entity,
52
+ project=config.wandb_project,
53
+ dir=config.wandb_save_dir
54
+ )
55
+
56
+ self.output_path = config.logdir
57
+
58
+ # Step 2: Initialize the model and optimizer
59
+
60
+ assert config.distribution_loss == "ode", "Only ODE loss is supported for ODE training"
61
+ self.model = ODERegression(config, device=self.device)
62
+
63
+ self.model.generator = fsdp_wrap(
64
+ self.model.generator,
65
+ sharding_strategy=config.sharding_strategy,
66
+ mixed_precision=config.mixed_precision,
67
+ wrap_strategy=config.generator_fsdp_wrap_strategy
68
+ )
69
+ self.model.text_encoder = fsdp_wrap(
70
+ self.model.text_encoder,
71
+ sharding_strategy=config.sharding_strategy,
72
+ mixed_precision=config.mixed_precision,
73
+ wrap_strategy=config.text_encoder_fsdp_wrap_strategy,
74
+ cpu_offload=getattr(config, "text_encoder_cpu_offload", False)
75
+ )
76
+
77
+ if not config.no_visualize or config.load_raw_video:
78
+ self.model.vae = self.model.vae.to(
79
+ device=self.device, dtype=torch.bfloat16 if config.mixed_precision else torch.float32)
80
+
81
+ self.generator_optimizer = torch.optim.AdamW(
82
+ [param for param in self.model.generator.parameters()
83
+ if param.requires_grad],
84
+ lr=config.lr,
85
+ betas=(config.beta1, config.beta2),
86
+ weight_decay=config.weight_decay
87
+ )
88
+
89
+ # Step 3: Initialize the dataloader
90
+ dataset = ODERegressionLMDBDataset(
91
+ config.data_path, max_pair=getattr(config, "max_pair", int(1e8)))
92
+ sampler = torch.utils.data.distributed.DistributedSampler(
93
+ dataset, shuffle=True, drop_last=True)
94
+ dataloader = torch.utils.data.DataLoader(
95
+ dataset, batch_size=config.batch_size, sampler=sampler, num_workers=8)
96
+ total_batch_size = getattr(config, "total_batch_size", None)
97
+ if total_batch_size is not None:
98
+ assert total_batch_size == config.batch_size * self.world_size, "Gradient accumulation is not supported for ODE training"
99
+ self.dataloader = cycle(dataloader)
100
+
101
+ self.step = 0
102
+
103
+ ##############################################################################################################
104
+ # 7. (If resuming) Load the model and optimizer, lr_scheduler, ema's statedicts
105
+ if getattr(config, "generator_ckpt", False):
106
+ print(f"Loading pretrained generator from {config.generator_ckpt}")
107
+ state_dict = torch.load(config.generator_ckpt, map_location="cpu")[
108
+ 'generator']
109
+ self.model.generator.load_state_dict(
110
+ state_dict, strict=True
111
+ )
112
+
113
+ ##############################################################################################################
114
+
115
+ self.max_grad_norm = 10.0
116
+ self.previous_time = None
117
+
118
+ def save(self):
119
+ print("Start gathering distributed model states...")
120
+ generator_state_dict = fsdp_state_dict(
121
+ self.model.generator)
122
+ state_dict = {
123
+ "generator": generator_state_dict
124
+ }
125
+
126
+ if self.is_main_process:
127
+ os.makedirs(os.path.join(self.output_path,
128
+ f"checkpoint_model_{self.step:06d}"), exist_ok=True)
129
+ torch.save(state_dict, os.path.join(self.output_path,
130
+ f"checkpoint_model_{self.step:06d}", "model.pt"))
131
+ print("Model saved to", os.path.join(self.output_path,
132
+ f"checkpoint_model_{self.step:06d}", "model.pt"))
133
+
134
+ def train_one_step(self):
135
+ VISUALIZE = self.step % 100 == 0
136
+ self.model.eval() # prevent any randomness (e.g. dropout)
137
+
138
+ # Step 1: Get the next batch of text prompts
139
+ batch = next(self.dataloader)
140
+ text_prompts = batch["prompts"]
141
+ ode_latent = batch["ode_latent"].to(
142
+ device=self.device, dtype=self.dtype)
143
+
144
+ # Step 2: Extract the conditional infos
145
+ with torch.no_grad():
146
+ conditional_dict = self.model.text_encoder(
147
+ text_prompts=text_prompts)
148
+
149
+ # Step 3: Train the generator
150
+ generator_loss, log_dict = self.model.generator_loss(
151
+ ode_latent=ode_latent,
152
+ conditional_dict=conditional_dict
153
+ )
154
+
155
+ unnormalized_loss = log_dict["unnormalized_loss"]
156
+ timestep = log_dict["timestep"]
157
+
158
+ if self.world_size > 1:
159
+ gathered_unnormalized_loss = torch.zeros(
160
+ [self.world_size, *unnormalized_loss.shape],
161
+ dtype=unnormalized_loss.dtype, device=self.device)
162
+ gathered_timestep = torch.zeros(
163
+ [self.world_size, *timestep.shape],
164
+ dtype=timestep.dtype, device=self.device)
165
+
166
+ dist.all_gather_into_tensor(
167
+ gathered_unnormalized_loss, unnormalized_loss)
168
+ dist.all_gather_into_tensor(gathered_timestep, timestep)
169
+ else:
170
+ gathered_unnormalized_loss = unnormalized_loss
171
+ gathered_timestep = timestep
172
+
173
+ loss_breakdown = defaultdict(list)
174
+ stats = {}
175
+
176
+ for index, t in enumerate(timestep):
177
+ loss_breakdown[str(int(t.item()) // 250 * 250)].append(
178
+ unnormalized_loss[index].item())
179
+
180
+ for key_t in loss_breakdown.keys():
181
+ stats["loss_at_time_" + key_t] = sum(loss_breakdown[key_t]) / \
182
+ len(loss_breakdown[key_t])
183
+
184
+ self.generator_optimizer.zero_grad()
185
+ generator_loss.backward()
186
+ generator_grad_norm = self.model.generator.clip_grad_norm_(
187
+ self.max_grad_norm)
188
+ self.generator_optimizer.step()
189
+
190
+ # Step 4: Visualization
191
+ if VISUALIZE and not self.config.no_visualize and not self.config.disable_wandb and self.is_main_process:
192
+ # Visualize the input, output, and ground truth
193
+ input = log_dict["input"]
194
+ output = log_dict["output"]
195
+ ground_truth = ode_latent[:, -1]
196
+
197
+ input_video = self.model.vae.decode_to_pixel(input)
198
+ output_video = self.model.vae.decode_to_pixel(output)
199
+ ground_truth_video = self.model.vae.decode_to_pixel(ground_truth)
200
+ input_video = 255.0 * (input_video.cpu().numpy() * 0.5 + 0.5)
201
+ output_video = 255.0 * (output_video.cpu().numpy() * 0.5 + 0.5)
202
+ ground_truth_video = 255.0 * (ground_truth_video.cpu().numpy() * 0.5 + 0.5)
203
+
204
+ # Visualize the input, output, and ground truth
205
+ wandb.log({
206
+ "input": wandb.Video(input_video, caption="Input", fps=16, format="mp4"),
207
+ "output": wandb.Video(output_video, caption="Output", fps=16, format="mp4"),
208
+ "ground_truth": wandb.Video(ground_truth_video, caption="Ground Truth", fps=16, format="mp4"),
209
+ }, step=self.step)
210
+
211
+ # Step 5: Logging
212
+ if self.is_main_process and not self.disable_wandb:
213
+ wandb_loss_dict = {
214
+ "generator_loss": generator_loss.item(),
215
+ "generator_grad_norm": generator_grad_norm.item(),
216
+ **stats
217
+ }
218
+ wandb.log(wandb_loss_dict, step=self.step)
219
+
220
+ if self.step % self.config.gc_interval == 0:
221
+ if dist.get_rank() == 0:
222
+ logging.info("DistGarbageCollector: Running GC.")
223
+ gc.collect()
224
+
225
+ def train(self):
226
+ while True:
227
+ self.train_one_step()
228
+ if (not self.config.no_save) and self.step % self.config.log_iters == 0:
229
+ self.save()
230
+ torch.cuda.empty_cache()
231
+
232
+ barrier()
233
+ if self.is_main_process:
234
+ current_time = time.time()
235
+ if self.previous_time is None:
236
+ self.previous_time = current_time
237
+ else:
238
+ if not self.disable_wandb:
239
+ wandb.log({"per iteration time": current_time - self.previous_time}, step=self.step)
240
+ self.previous_time = current_time
241
+
242
+ self.step += 1