Add DeMemWM curriculum training support
Browse files- configurations/experiment/exp_video.yaml +11 -0
- experiments/exp_base.py +115 -77
- main.py +1 -1
- tests/test_resume_checkpoint_logic.py +81 -1
- utils/distributed_utils.py +6 -1
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 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|