BonanDing commited on
Commit
b165569
·
1 Parent(s): 7283dd2

Add DeMemWM curriculum training support

Browse files
configurations/experiment/exp_video.yaml CHANGED
@@ -9,6 +9,17 @@ training:
9
  batch_size: 4
10
  max_epochs: -1
11
  max_steps: 2000005
 
 
 
 
 
 
 
 
 
 
 
12
  checkpointing:
13
  every_n_train_steps: 2500
14
  optim:
 
9
  batch_size: 4
10
  max_epochs: -1
11
  max_steps: 2000005
12
+ curriculum:
13
+ enabled: true
14
+ stages:
15
+ - name: no_updown
16
+ until_step: 240000
17
+ dataset:
18
+ wo_updown: true
19
+ - name: with_updown
20
+ until_step: ${experiment.training.max_steps}
21
+ dataset:
22
+ wo_updown: false
23
  checkpointing:
24
  every_n_train_steps: 2500
25
  optim:
experiments/exp_base.py CHANGED
@@ -21,7 +21,7 @@ from lightning.pytorch.utilities.types import TRAIN_DATALOADERS
21
  from lightning.pytorch.callbacks import LearningRateMonitor, ModelCheckpoint
22
  from pytorch_lightning.utilities import rank_zero_info
23
 
24
- from omegaconf import DictConfig
25
 
26
  from utils.print_utils import cyan
27
  from utils.distributed_utils import is_rank_zero
@@ -395,6 +395,108 @@ class BaseLightningExperiment(BaseExperiment):
395
  callbacks = []
396
  if self.logger:
397
  callbacks.append(LearningRateMonitor("step", True))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
 
399
  def _build_training_loader(self) -> Optional[Union[TRAIN_DATALOADERS, pl.LightningDataModule]]:
400
  train_dataset = self._build_dataset("training")
@@ -453,84 +555,20 @@ class BaseLightningExperiment(BaseExperiment):
453
  if self.cfg.training.compile:
454
  self.algo = torch.compile(self.algo)
455
 
456
- callbacks = []
457
- if self.logger:
458
- callbacks.append(LearningRateMonitor("step", True))
459
- if "checkpointing" in self.cfg.training:
460
- callbacks.append(
461
- ModelCheckpoint(
462
- pathlib.Path(hydra.core.hydra_config.HydraConfig.get()["runtime"]["output_dir"]) / "checkpoints",
463
- filename="epoch{epoch}_step{step}",
464
- auto_insert_metric_name=False,
465
- **self.cfg.training.checkpointing,
466
- )
467
- )
468
-
469
- trainer = pl.Trainer(
470
- accelerator="auto",
471
- devices="auto",
472
- strategy=DDPStrategy(find_unused_parameters=True) if torch.cuda.device_count() > 1 else "auto",
473
- logger=self.logger or False,
474
- callbacks=callbacks,
475
- gradient_clip_val=self.cfg.training.optim.gradient_clip_val or 0.0,
476
- val_check_interval=self.cfg.validation.val_every_n_step if self.cfg.validation.val_every_n_step else None,
477
- limit_val_batches=self.cfg.validation.limit_batch,
478
- check_val_every_n_epoch=self.cfg.validation.val_every_n_epoch if not self.cfg.validation.val_every_n_step else None,
479
- accumulate_grad_batches=self.cfg.training.optim.accumulate_grad_batches or 1,
480
- precision=self.cfg.training.precision or 32,
481
- detect_anomaly=False,
482
- num_sanity_val_steps=int(self.cfg.debug) if self.cfg.debug else 0,
483
- max_epochs=self.cfg.training.max_epochs,
484
- max_steps=self.cfg.training.max_steps,
485
- max_time=self.cfg.training.max_time
486
  )
487
 
488
-
489
- if self.auto_resuming:
490
- trainer.fit(
491
- self.algo,
492
- train_dataloaders=self._build_training_loader(),
493
- val_dataloaders=self._build_validation_loader(),
494
- ckpt_path=self.ckpt_path,
495
- )
496
- elif self.customized_load:
497
- self._load_custom_checkpoint_for_task()
498
-
499
- if self.zero_init_gate:
500
- for name, para in self.algo.diffusion_model.named_parameters():
501
- if 'r_adaLN_modulation' in name:
502
- para.requires_grad_(False)
503
- para[2*1024:3*1024] = 0
504
- para[5*1024:6*1024] = 0
505
- para.requires_grad_(True)
506
-
507
- if self.only_tune_memory:
508
- for name, para in self.algo.diffusion_model.named_parameters():
509
- para.requires_grad_(False)
510
- if 'r_' in name or 'pose_embedder' in name or 'pose_cond_mlp' in name or 'lora_' in name:
511
- para.requires_grad_(True)
512
-
513
- trainer.fit(
514
- self.algo,
515
- train_dataloaders=self._build_training_loader(),
516
- val_dataloaders=self._build_validation_loader(),
517
- ckpt_path=None,
518
- )
519
- else:
520
-
521
- if self.only_tune_memory:
522
- for name, para in self.algo.diffusion_model.named_parameters():
523
- para.requires_grad_(False)
524
- if 'r_' in name or 'pose_embedder' in name or 'pose_cond_mlp' in name or 'lora_' in name:
525
- para.requires_grad_(True)
526
-
527
- trainer.fit(
528
- self.algo,
529
- train_dataloaders=self._build_training_loader(),
530
- val_dataloaders=self._build_validation_loader(),
531
- ckpt_path=self.ckpt_path,
532
- )
533
-
534
  def validation(self) -> None:
535
  """
536
  All validation happens here
 
21
  from lightning.pytorch.callbacks import LearningRateMonitor, ModelCheckpoint
22
  from pytorch_lightning.utilities import rank_zero_info
23
 
24
+ from omegaconf import DictConfig, open_dict
25
 
26
  from utils.print_utils import cyan
27
  from utils.distributed_utils import is_rank_zero
 
395
  callbacks = []
396
  if self.logger:
397
  callbacks.append(LearningRateMonitor("step", True))
398
+ if "checkpointing" in self.cfg.training:
399
+ callbacks.append(
400
+ ModelCheckpoint(
401
+ pathlib.Path(hydra.core.hydra_config.HydraConfig.get()["runtime"]["output_dir"]) / "checkpoints",
402
+ filename="epoch{epoch}_step{step}",
403
+ auto_insert_metric_name=False,
404
+ **self.cfg.training.checkpointing,
405
+ )
406
+ )
407
+ return callbacks
408
+
409
+ def _build_training_trainer(self, max_steps: int):
410
+ return pl.Trainer(
411
+ accelerator="auto",
412
+ devices="auto",
413
+ strategy=DDPStrategy(find_unused_parameters=True) if torch.cuda.device_count() > 1 else "auto",
414
+ logger=self.logger or False,
415
+ callbacks=self._build_trainer_callbacks(),
416
+ gradient_clip_val=self.cfg.training.optim.gradient_clip_val or 0.0,
417
+ val_check_interval=self.cfg.validation.val_every_n_step if self.cfg.validation.val_every_n_step else None,
418
+ limit_val_batches=self.cfg.validation.limit_batch,
419
+ check_val_every_n_epoch=self.cfg.validation.val_every_n_epoch if not self.cfg.validation.val_every_n_step else None,
420
+ accumulate_grad_batches=self.cfg.training.optim.accumulate_grad_batches or 1,
421
+ precision=self.cfg.training.precision or 32,
422
+ detect_anomaly=False,
423
+ num_sanity_val_steps=int(self.cfg.debug) if self.cfg.debug else 0,
424
+ max_epochs=self.cfg.training.max_epochs,
425
+ max_steps=max_steps,
426
+ max_time=self.cfg.training.max_time
427
+ )
428
+
429
+ def _curriculum_stages(self):
430
+ curriculum = self.cfg.training.get("curriculum", None)
431
+ if curriculum is None or not bool(curriculum.get("enabled", True)):
432
+ return None
433
+ stages = curriculum.get("stages", None)
434
+ return stages if stages is not None and len(stages) > 0 else None
435
+
436
+ def _merge_curriculum_updates(self, target_cfg: DictConfig, updates: DictConfig) -> None:
437
+ with open_dict(target_cfg):
438
+ for key, value in updates.items():
439
+ if key in target_cfg and isinstance(target_cfg[key], DictConfig) and isinstance(value, DictConfig):
440
+ self._merge_curriculum_updates(target_cfg[key], value)
441
+ else:
442
+ target_cfg[key] = value
443
+
444
+ def _apply_curriculum_stage(self, stage: DictConfig, stage_idx: int, until_step: int) -> None:
445
+ for cfg_name in ("dataset", "algorithm"):
446
+ updates = stage.get(cfg_name, None)
447
+ if updates is not None:
448
+ self._merge_curriculum_updates(self.root_cfg[cfg_name], updates)
449
+ if is_rank_zero:
450
+ print(cyan("Curriculum stage:"), f"{stage.get('name', stage_idx + 1)} until step {until_step}")
451
+
452
+ def _curriculum_until_step(self, stage: DictConfig, stage_idx: int) -> int:
453
+ if "until_step" not in stage:
454
+ raise ValueError(f"experiment.training.curriculum.stages[{stage_idx}] must define until_step")
455
+ return int(stage.until_step)
456
+
457
+ def _curriculum_checkpoint_path(self, stage: DictConfig, stage_idx: int, until_step: int) -> pathlib.Path:
458
+ checkpoint_dir = pathlib.Path(hydra.core.hydra_config.HydraConfig.get()["runtime"]["output_dir"]) / "checkpoints"
459
+ stage_name = str(stage.get("name", f"stage_{stage_idx + 1}"))
460
+ safe_name = "".join(ch if ch.isalnum() or ch in "._-" else "_" for ch in stage_name)
461
+ return checkpoint_dir / f"curriculum_{stage_idx + 1:02d}_{safe_name}_step{until_step}.ckpt"
462
+
463
+ def _prepare_training_weights(self):
464
+ if self.auto_resuming:
465
+ return self.ckpt_path
466
+ if self.customized_load:
467
+ self._load_custom_checkpoint_for_task()
468
+
469
+ if self.zero_init_gate:
470
+ for name, para in self.algo.diffusion_model.named_parameters():
471
+ if 'r_adaLN_modulation' in name:
472
+ para.requires_grad_(False)
473
+ para[2*1024:3*1024] = 0
474
+ para[5*1024:6*1024] = 0
475
+ para.requires_grad_(True)
476
+
477
+ if self.only_tune_memory:
478
+ for name, para in self.algo.diffusion_model.named_parameters():
479
+ para.requires_grad_(False)
480
+ if 'r_' in name or 'pose_embedder' in name or 'pose_cond_mlp' in name or 'lora_' in name:
481
+ para.requires_grad_(True)
482
+
483
+ return None if self.customized_load else self.ckpt_path
484
+
485
+ def _fit_training_curriculum(self, stages, ckpt_path) -> None:
486
+ for stage_idx, stage in enumerate(stages):
487
+ until_step = self._curriculum_until_step(stage, stage_idx)
488
+ self._apply_curriculum_stage(stage, stage_idx, until_step)
489
+ trainer = self._build_training_trainer(max_steps=until_step)
490
+ trainer.fit(
491
+ self.algo,
492
+ train_dataloaders=self._build_training_loader(),
493
+ val_dataloaders=self._build_validation_loader(),
494
+ ckpt_path=ckpt_path,
495
+ )
496
+ if stage_idx + 1 < len(stages):
497
+ ckpt_path = self._curriculum_checkpoint_path(stage, stage_idx, until_step)
498
+ ckpt_path.parent.mkdir(parents=True, exist_ok=True)
499
+ trainer.save_checkpoint(ckpt_path)
500
 
501
  def _build_training_loader(self) -> Optional[Union[TRAIN_DATALOADERS, pl.LightningDataModule]]:
502
  train_dataset = self._build_dataset("training")
 
555
  if self.cfg.training.compile:
556
  self.algo = torch.compile(self.algo)
557
 
558
+ ckpt_path = self._prepare_training_weights()
559
+ curriculum_stages = self._curriculum_stages()
560
+ if curriculum_stages is not None:
561
+ self._fit_training_curriculum(curriculum_stages, ckpt_path)
562
+ return
563
+
564
+ trainer = self._build_training_trainer(max_steps=self.cfg.training.max_steps)
565
+ trainer.fit(
566
+ self.algo,
567
+ train_dataloaders=self._build_training_loader(),
568
+ val_dataloaders=self._build_validation_loader(),
569
+ ckpt_path=ckpt_path,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
  )
571
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
  def validation(self) -> None:
573
  """
574
  All validation happens here
main.py CHANGED
@@ -29,7 +29,7 @@ WANDB_RUN_ID_FILE = ".wandb_run_id"
29
 
30
 
31
  def _process_rank() -> int:
32
- return int(os.environ.get("RANK", 0))
33
 
34
 
35
  def _checkpoint_step(path: Path) -> int:
 
29
 
30
 
31
  def _process_rank() -> int:
32
+ return int(os.environ.get("RANK", os.environ.get("SLURM_PROCID", 0)))
33
 
34
 
35
  def _checkpoint_step(path: Path) -> int:
tests/test_resume_checkpoint_logic.py CHANGED
@@ -1,4 +1,7 @@
 
1
  import unittest
 
 
2
  from unittest.mock import patch
3
 
4
  from omegaconf import OmegaConf
@@ -21,12 +24,14 @@ class DummyExperiment(exp_base.BaseLightningExperiment):
21
 
22
 
23
  class DummyTrainer:
 
24
  fit_calls = []
25
  validate_calls = []
26
  test_calls = []
 
27
 
28
  def __init__(self, *args, **kwargs):
29
- pass
30
 
31
  def fit(self, *args, **kwargs):
32
  self.fit_calls.append({"args": args, "kwargs": kwargs})
@@ -37,6 +42,9 @@ class DummyTrainer:
37
  def test(self, *args, **kwargs):
38
  self.test_calls.append({"args": args, "kwargs": kwargs})
39
 
 
 
 
40
 
41
  def _root_cfg(auto_resuming=True, algorithm_name="dummy", zero_init_gate=False, only_tune_memory=False):
42
  return OmegaConf.create(
@@ -120,6 +128,62 @@ class ResumeCheckpointLogicTests(unittest.TestCase):
120
 
121
  self.assertEqual(DummyTrainer.fit_calls[0]["kwargs"]["ckpt_path"], "/tmp/last.ckpt")
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  def test_validation_load_checkpoint_takes_priority_over_separate_load(self):
124
  DummyTrainer.validate_calls = []
125
  experiment = DummyExperiment(_root_cfg(auto_resuming=False), logger=None, ckpt_path="/tmp/trained.ckpt")
@@ -142,6 +206,22 @@ class ResumeCheckpointLogicTests(unittest.TestCase):
142
  load_mock.assert_called_once_with(algo=experiment.algo, checkpoint_path="/tmp/trained.ckpt")
143
  self.assertIsNone(DummyTrainer.test_calls[0]["kwargs"]["ckpt_path"])
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
  if __name__ == "__main__":
147
  unittest.main()
 
1
+ import importlib
2
  import unittest
3
+ import os
4
+ from pathlib import Path
5
  from unittest.mock import patch
6
 
7
  from omegaconf import OmegaConf
 
24
 
25
 
26
  class DummyTrainer:
27
+ init_calls = []
28
  fit_calls = []
29
  validate_calls = []
30
  test_calls = []
31
+ save_checkpoint_calls = []
32
 
33
  def __init__(self, *args, **kwargs):
34
+ self.init_calls.append({"args": args, "kwargs": kwargs})
35
 
36
  def fit(self, *args, **kwargs):
37
  self.fit_calls.append({"args": args, "kwargs": kwargs})
 
42
  def test(self, *args, **kwargs):
43
  self.test_calls.append({"args": args, "kwargs": kwargs})
44
 
45
+ def save_checkpoint(self, path):
46
+ self.save_checkpoint_calls.append(path)
47
+
48
 
49
  def _root_cfg(auto_resuming=True, algorithm_name="dummy", zero_init_gate=False, only_tune_memory=False):
50
  return OmegaConf.create(
 
128
 
129
  self.assertEqual(DummyTrainer.fit_calls[0]["kwargs"]["ckpt_path"], "/tmp/last.ckpt")
130
 
131
+ def test_curriculum_updates_stage_config_and_checkpoint_handoff(self):
132
+ DummyTrainer.init_calls = []
133
+ DummyTrainer.fit_calls = []
134
+ DummyTrainer.save_checkpoint_calls = []
135
+ cfg = _root_cfg(auto_resuming=True)
136
+ cfg.experiment.training.curriculum = {
137
+ "enabled": True,
138
+ "stages": [
139
+ {
140
+ "name": "near",
141
+ "until_step": 3,
142
+ "dataset": {"wo_updown": True, "memory_selection": {"pose_similarity_radius": 2.0}},
143
+ "algorithm": {"memory_selection": {"pose_similarity_radius": 2.0}},
144
+ },
145
+ {
146
+ "name": "full",
147
+ "until_step": 5,
148
+ "dataset": {"wo_updown": False, "memory_selection": {"pose_similarity_radius": 8.0}},
149
+ "algorithm": {"memory_selection": {"pose_similarity_radius": 8.0}},
150
+ },
151
+ ],
152
+ }
153
+ experiment = DummyExperiment(cfg, logger=None, ckpt_path="/tmp/last.ckpt")
154
+ experiment.algo = object()
155
+ seen_dataset = []
156
+
157
+ def build_training_loader():
158
+ seen_dataset.append(
159
+ (
160
+ bool(experiment.root_cfg.dataset.wo_updown),
161
+ float(experiment.root_cfg.dataset.memory_selection.pose_similarity_radius),
162
+ )
163
+ )
164
+ return None
165
+
166
+ experiment._build_training_loader = build_training_loader
167
+
168
+ with patch.object(exp_base.pl, "Trainer", DummyTrainer), patch.object(
169
+ DummyExperiment,
170
+ "_curriculum_checkpoint_path",
171
+ return_value=Path("/tmp/curriculum_stage.ckpt"),
172
+ ):
173
+ experiment.training()
174
+
175
+ self.assertEqual(seen_dataset, [(True, 2.0), (False, 8.0)])
176
+ self.assertEqual(
177
+ [call["kwargs"]["ckpt_path"] for call in DummyTrainer.fit_calls],
178
+ ["/tmp/last.ckpt", Path("/tmp/curriculum_stage.ckpt")],
179
+ )
180
+ self.assertEqual(
181
+ [call["kwargs"]["max_steps"] for call in DummyTrainer.init_calls],
182
+ [3, 5],
183
+ )
184
+ self.assertEqual(DummyTrainer.save_checkpoint_calls, [Path("/tmp/curriculum_stage.ckpt")])
185
+ self.assertEqual(float(experiment.root_cfg.algorithm.memory_selection.pose_similarity_radius), 8.0)
186
+
187
  def test_validation_load_checkpoint_takes_priority_over_separate_load(self):
188
  DummyTrainer.validate_calls = []
189
  experiment = DummyExperiment(_root_cfg(auto_resuming=False), logger=None, ckpt_path="/tmp/trained.ckpt")
 
206
  load_mock.assert_called_once_with(algo=experiment.algo, checkpoint_path="/tmp/trained.ckpt")
207
  self.assertIsNone(DummyTrainer.test_calls[0]["kwargs"]["ckpt_path"])
208
 
209
+ def test_pre_lightning_rank_uses_slurm_procid(self):
210
+ import main
211
+ import utils.distributed_utils as distributed_utils
212
+
213
+ old_env = os.environ.copy()
214
+ try:
215
+ os.environ.pop("RANK", None)
216
+ os.environ["SLURM_PROCID"] = "1"
217
+ self.assertEqual(main._process_rank(), 1)
218
+ reloaded = importlib.reload(distributed_utils)
219
+ self.assertFalse(reloaded.is_rank_zero)
220
+ finally:
221
+ os.environ.clear()
222
+ os.environ.update(old_env)
223
+ importlib.reload(distributed_utils)
224
+
225
 
226
  if __name__ == "__main__":
227
  unittest.main()
utils/distributed_utils.py CHANGED
@@ -1,3 +1,8 @@
1
  import os
2
 
3
- is_rank_zero = int(os.environ.get("RANK", 0)) == 0
 
 
 
 
 
 
1
  import os
2
 
3
+
4
+ def process_rank() -> int:
5
+ return int(os.environ.get("RANK", os.environ.get("SLURM_PROCID", 0)))
6
+
7
+
8
+ is_rank_zero = process_rank() == 0