| """ |
| This repo is forked from [Boyuan Chen](https://boyuan.space/)'s research |
| template [repo](https://github.com/buoyancy99/research-template). |
| By its MIT license, you must keep the above sentence in `README.md` |
| and the `LICENSE` file to credit the author. |
| """ |
|
|
| from abc import ABC, abstractmethod |
| from typing import Optional, Union, Literal, List, Dict |
| import pathlib |
| import os |
| import re |
| from datetime import timedelta |
|
|
| import hydra |
| import torch |
| from lightning.pytorch.strategies.ddp import DDPStrategy |
|
|
| import lightning.pytorch as pl |
| from lightning.pytorch.loggers.wandb import WandbLogger |
| from lightning.pytorch.utilities.types import TRAIN_DATALOADERS |
| from lightning.pytorch.callbacks import LearningRateMonitor, ModelCheckpoint |
| from pytorch_lightning.utilities import rank_zero_info |
|
|
| from omegaconf import DictConfig, open_dict |
|
|
| from utils.print_utils import cyan |
| from utils.distributed_utils import is_rank_zero |
| from safetensors.torch import load_file |
| from pathlib import Path |
| from huggingface_hub import hf_hub_download |
| from huggingface_hub import model_info |
|
|
| torch.set_float32_matmul_precision("high") |
|
|
|
|
| class LastKStepModelCheckpoint(ModelCheckpoint): |
| """ModelCheckpoint variant that keeps only the latest k step checkpoints.""" |
|
|
| def __init__(self, *args, save_last_k: Optional[int] = None, **kwargs): |
| self._save_last_k = None |
| if save_last_k is not None: |
| self._save_last_k = int(save_last_k) |
| if self._save_last_k < 1: |
| raise ValueError("save_last_k must be a positive integer") |
| |
| kwargs["save_top_k"] = -1 |
| super().__init__(*args, **kwargs) |
|
|
| def _save_checkpoint(self, trainer: "pl.Trainer", filepath: str) -> None: |
| super()._save_checkpoint(trainer, filepath) |
| self._prune_old_step_checkpoints(trainer) |
|
|
| def _prune_old_step_checkpoints(self, trainer: "pl.Trainer") -> None: |
| if self._save_last_k is None or self.dirpath is None: |
| return |
|
|
| checkpoint_dir = pathlib.Path(self.dirpath) |
| if not checkpoint_dir.exists(): |
| return |
|
|
| step_checkpoints = [] |
| for checkpoint_path in checkpoint_dir.glob("*.ckpt"): |
| match = re.search(r"(?:^|[_=-])step[=_]?(\d+)", checkpoint_path.stem) |
| if match: |
| step_checkpoints.append((int(match.group(1)), checkpoint_path.name, str(checkpoint_path))) |
|
|
| for _, _, checkpoint_path in sorted(step_checkpoints)[:-self._save_last_k]: |
| self._remove_checkpoint(trainer, checkpoint_path) |
|
|
| def is_huggingface_model(path: str) -> bool: |
| hf_ckpt = str(path).split('/') |
| repo_id = '/'.join(hf_ckpt[:2]) |
| try: |
| model_info(repo_id) |
| return True |
| except: |
| return False |
| |
| _IGNORED_STATE_KEYS = {"data_mean", "data_std"} |
| _WRAPPER_PREFIXES = ("module.", "_orig_mod.") |
| _SOURCE_PREFIXES = ( |
| "", |
| "algo.", |
| "diffusion_model.model.", |
| "diffusion_model.", |
| "model.", |
| "vae.", |
| "pose_prediction_model.", |
| ) |
| _TARGET_PREFIXES = ( |
| "", |
| "_orig_mod.", |
| "model.", |
| "diffusion_model.", |
| "diffusion_model.model.", |
| "vae.", |
| "pose_prediction_model.", |
| ) |
| _DEMEMWM_FRAME_MEMORY_MARKERS = ( |
| "r_attn_anchor", |
| "r_attn_dynamic", |
| "r_attn_revisit", |
| "r_adaLN_modulation", |
| "r_mlp", |
| "frame_memory", |
| "memory_adapter", |
| ) |
|
|
|
|
| def _checkpoint_step_from_path(ckpt_path) -> Optional[int]: |
| if ckpt_path is None: |
| return None |
| step_match = re.search(r"step[=_-]?(\d+)", pathlib.Path(str(ckpt_path)).stem) |
| return int(step_match.group(1)) if step_match else None |
|
|
|
|
| def _short_list(items, limit=8): |
| items = list(items) |
| suffix = "" if len(items) <= limit else f", ... (+{len(items) - limit} more)" |
| return ", ".join(str(item) for item in items[:limit]) + suffix |
|
|
|
|
| def _extract_state_dict(ckpt, checkpoint_path): |
| if isinstance(ckpt, dict): |
| for key in ("state_dict", "model_state_dict", "model"): |
| value = ckpt.get(key) |
| if isinstance(value, dict): |
| return value |
| return ckpt |
| raise ValueError(f"Checkpoint {checkpoint_path} does not contain a state dict.") |
|
|
|
|
| def _read_checkpoint_state_dict(checkpoint_path): |
| if checkpoint_path.suffix == ".safetensors": |
| return load_file(checkpoint_path) |
| if checkpoint_path.suffix == ".pt": |
| return _extract_state_dict( |
| torch.load(checkpoint_path, map_location=torch.device("cpu"), weights_only=True), |
| checkpoint_path, |
| ) |
| if checkpoint_path.suffix == ".ckpt": |
| return _extract_state_dict( |
| |
| |
| torch.load(checkpoint_path, map_location=torch.device("cpu"), weights_only=False), |
| checkpoint_path, |
| ) |
| raise ValueError(f"Unsupported checkpoint: {checkpoint_path}") |
|
|
|
|
| def _load_checkpoint_state_dict(checkpoint_path): |
| if not checkpoint_path.is_dir(): |
| return _read_checkpoint_state_dict(checkpoint_path), checkpoint_path |
|
|
| ckpt_files = sorted(checkpoint_path.glob("*.ckpt"), key=lambda p: p.stat().st_mtime, reverse=True) |
| if not ckpt_files: |
| raise FileNotFoundError("No .ckpt files found in the specified directory!") |
|
|
| errors = [] |
| for ckpt_file in ckpt_files: |
| try: |
| state_dict = _read_checkpoint_state_dict(ckpt_file) |
| if any(torch.is_tensor(value) for value in state_dict.values()): |
| rank_zero_info(f"Checkpoint file selected for loading: {ckpt_file}") |
| return state_dict, ckpt_file |
| errors.append(f"{ckpt_file.name}: no tensor entries") |
| except Exception as exc: |
| errors.append(f"{ckpt_file.name}: {exc}") |
| raise RuntimeError(f"No readable .ckpt files found in {checkpoint_path}: {_short_list(errors, limit=4)}") |
|
|
|
|
| def _clean_state_dict(state_dict): |
| clean_state_dict = {} |
| ignored_keys, non_tensor_keys = [], [] |
| for key, value in state_dict.items(): |
| key = str(key) |
| if key.split(".")[-1] in _IGNORED_STATE_KEYS: |
| ignored_keys.append(key) |
| elif not torch.is_tensor(value): |
| non_tensor_keys.append(key) |
| else: |
| clean_state_dict[key] = value |
|
|
| if ignored_keys: |
| rank_zero_info(f"Ignored checkpoint statistics keys: {_short_list(ignored_keys)}") |
| if non_tensor_keys: |
| rank_zero_info(f"Ignored non-tensor checkpoint entries: {_short_list(non_tensor_keys)}") |
| if not clean_state_dict: |
| raise ValueError("Checkpoint contains no tensor weights after filtering.") |
| return clean_state_dict |
|
|
|
|
| def _strip_wrapper_prefixes(key): |
| changed = True |
| while changed: |
| changed = False |
| for prefix in _WRAPPER_PREFIXES: |
| if key.startswith(prefix): |
| key = key[len(prefix):] |
| changed = True |
| return key |
|
|
|
|
| def _best_prefix_match(state_dict, target_state): |
| source_prefixes = [ |
| prefix for prefix in _SOURCE_PREFIXES |
| if prefix == "" or any(_strip_wrapper_prefixes(key).startswith(prefix) for key in state_dict) |
| ] |
| target_prefixes = [ |
| prefix for prefix in _TARGET_PREFIXES |
| if prefix == "" or any(key.startswith(prefix) for key in target_state) |
| ] |
|
|
| best = None |
| for source_prefix in source_prefixes: |
| for target_prefix in target_prefixes: |
| mapped, collisions = {}, 0 |
| for source_key, value in state_dict.items(): |
| key = _strip_wrapper_prefixes(source_key) |
| if source_prefix and key.startswith(source_prefix): |
| key = key[len(source_prefix):] |
| target_key = target_prefix + key |
| if target_key in mapped: |
| collisions += 1 |
| continue |
| mapped[target_key] = (source_key, value) |
| matched = [key for key in mapped if key in target_state] |
| shape_matched = [ |
| key for key in matched |
| if tuple(mapped[key][1].shape) == tuple(target_state[key].shape) |
| ] |
| score = (len(shape_matched), len(matched), -collisions, source_prefix == "", target_prefix == "") |
| if best is None or score > best[0]: |
| best = (score, source_prefix, target_prefix, mapped) |
| return best[1], best[2], best[3] |
|
|
|
|
| def _is_dememwm_frame_memory_key(key): |
| return any(marker in key for marker in _DEMEMWM_FRAME_MEMORY_MARKERS) |
|
|
|
|
| def _zero_frame_memory_gate_slices(algo): |
| zeroed = [] |
| with torch.no_grad(): |
| for name, param in algo.named_parameters(): |
| if "r_adaLN_modulation" not in name or param.shape[0] % 6 != 0: |
| continue |
| chunk = param.shape[0] // 6 |
| param[2 * chunk:3 * chunk].zero_() |
| param[5 * chunk:6 * chunk].zero_() |
| zeroed.append(name) |
| return zeroed |
|
|
|
|
| def load_custom_checkpoint(algo, checkpoint_path): |
| if not checkpoint_path: |
| rank_zero_info("No checkpoint path provided, skipping checkpoint loading.") |
| return None |
|
|
| if not isinstance(checkpoint_path, Path): |
| checkpoint_path = Path(checkpoint_path) |
|
|
| if not checkpoint_path.exists() and is_huggingface_model(str(checkpoint_path)): |
| |
| hf_ckpt = str(checkpoint_path).split('/') |
| repo_id = '/'.join(hf_ckpt[:2]) |
| file_name = '/'.join(hf_ckpt[2:]) |
| model_path = hf_hub_download(repo_id=repo_id, filename=file_name) |
| checkpoint_path = Path(model_path) |
|
|
| raw_state_dict, loaded_path = _load_checkpoint_state_dict(checkpoint_path) |
| state_dict = _clean_state_dict(raw_state_dict) |
| target_state = algo.state_dict() |
| source_prefix, target_prefix, mapped_state = _best_prefix_match(state_dict, target_state) |
|
|
| compatible_state_dict, unexpected_keys, shape_mismatches = {}, [], [] |
| for target_key, (source_key, value) in mapped_state.items(): |
| if target_key not in target_state: |
| unexpected_keys.append(source_key) |
| continue |
| if tuple(value.shape) != tuple(target_state[target_key].shape): |
| shape_mismatches.append( |
| f"{source_key}->{target_key}: {tuple(value.shape)} != {tuple(target_state[target_key].shape)}" |
| ) |
| continue |
| compatible_state_dict[target_key] = value |
|
|
| missing_keys = [key for key in target_state if key not in compatible_state_dict] |
| checkpoint_is_dememwm = any(_is_dememwm_frame_memory_key(key) for key in state_dict) |
| dememwm_target_keys = [key for key in target_state if _is_dememwm_frame_memory_key(key)] |
| dememwm_missing = [key for key in dememwm_target_keys if key not in compatible_state_dict] |
|
|
| rank_zero_info( |
| "Checkpoint prefix match: " |
| f"strip '{source_prefix or '<none>'}', add '{target_prefix or '<none>'}'." |
| ) |
| if shape_mismatches: |
| rank_zero_info(f"Skipped shape-mismatched checkpoint keys: {_short_list(shape_mismatches)}") |
| if unexpected_keys: |
| rank_zero_info(f"Skipped unexpected checkpoint keys: {_short_list(unexpected_keys)}") |
| if missing_keys: |
| rank_zero_info(f"Missing checkpoint keys: {_short_list(missing_keys)}") |
| if not compatible_state_dict: |
| raise RuntimeError(f"No compatible tensor weights found in checkpoint {loaded_path}.") |
| if checkpoint_is_dememwm and dememwm_missing: |
| raise RuntimeError( |
| "Incomplete DeMemWM checkpoint: missing frame-memory keys " |
| f"{_short_list(dememwm_missing)}" |
| ) |
|
|
| algo.load_state_dict(compatible_state_dict, strict=False) |
| if dememwm_target_keys and not checkpoint_is_dememwm: |
| zeroed = _zero_frame_memory_gate_slices(algo) |
| if zeroed: |
| rank_zero_info(f"Zeroed frame-memory gate slices for base checkpoint load: {_short_list(zeroed)}") |
| rank_zero_info(f"Model weights loaded from {loaded_path}: {len(compatible_state_dict)} tensors.") |
|
|
| class BaseExperiment(ABC): |
| """ |
| Abstract class for an experiment. This generalizes the pytorch lightning Trainer & lightning Module to more |
| flexible experiments that doesn't fit in the typical ml loop, e.g. multi-stage reinforcement learning benchmarks. |
| """ |
|
|
| |
| compatible_algorithms: Dict = NotImplementedError |
|
|
| def __init__( |
| self, |
| root_cfg: DictConfig, |
| logger: Optional[WandbLogger] = None, |
| ckpt_path: Optional[Union[str, pathlib.Path]] = None, |
| ) -> None: |
| """ |
| Constructor |
| |
| Args: |
| cfg: configuration file that contains everything about the experiment |
| logger: a pytorch-lightning WandbLogger instance |
| ckpt_path: an optional path to saved checkpoint |
| """ |
| super().__init__() |
| self.root_cfg = root_cfg |
| self.cfg = root_cfg.experiment |
| self.debug = root_cfg.debug |
| self.logger = logger |
| self.ckpt_path = ckpt_path |
| self.algo = None |
| self.customized_load = getattr(root_cfg, "customized_load", False) |
| self.seperate_load = getattr(root_cfg, "seperate_load", False) |
| self.zero_init_gate= getattr(root_cfg, "zero_init_gate", False) |
| self.only_tune_memory = getattr(root_cfg, "only_tune_memory", False) |
| if self.root_cfg.algorithm._name == "dememwm_base": |
| if self.zero_init_gate: |
| raise ValueError("zero_init_gate is not supported for algorithm._name=dememwm_base.") |
| if self.only_tune_memory: |
| raise ValueError("only_tune_memory is not supported for algorithm._name=dememwm_base.") |
| self.diffusion_model_path = getattr(root_cfg, "diffusion_model_path", None) |
| self.vae_path = getattr(root_cfg, "vae_path", None) |
| self.pose_predictor_path = getattr(root_cfg, "pose_predictor_path", None) |
| self.auto_resuming = getattr(root_cfg, "_auto_resuming", False) |
|
|
| def _load_custom_checkpoint_for_task(self) -> None: |
| if self.ckpt_path: |
| load_custom_checkpoint(algo=self.algo, checkpoint_path=self.ckpt_path) |
| elif self.seperate_load: |
| if 'oasis500m' in self.diffusion_model_path: |
| load_custom_checkpoint(algo=self.algo.diffusion_model.model, checkpoint_path=self.diffusion_model_path) |
| else: |
| load_custom_checkpoint(algo=self.algo.diffusion_model, checkpoint_path=self.diffusion_model_path) |
| load_custom_checkpoint(algo=self.algo.vae, checkpoint_path=self.vae_path) |
| else: |
| load_custom_checkpoint(algo=self.algo, checkpoint_path=self.ckpt_path) |
|
|
| def _build_algo(self): |
| """ |
| Build the lightning module |
| :return: a pytorch-lightning module to be launched |
| """ |
| algo_name = self.root_cfg.algorithm._name |
| if algo_name not in self.compatible_algorithms: |
| raise ValueError( |
| f"Algorithm {algo_name} not found in compatible_algorithms for this Experiment class. " |
| "Make sure you define compatible_algorithms correctly and make sure that each key has " |
| "same name as yaml file under '[project_root]/configurations/algorithm' without .yaml suffix" |
| ) |
| return self.compatible_algorithms[algo_name](self.root_cfg.algorithm) |
|
|
| def exec_task(self, task: str) -> None: |
| """ |
| Executing a certain task specified by string. Each task should be a stage of experiment. |
| In most computer vision / nlp applications, tasks should be just train and test. |
| In reinforcement learning, you might have more stages such as collecting dataset etc |
| |
| Args: |
| task: a string specifying a task implemented for this experiment |
| """ |
| if hasattr(self, task) and callable(getattr(self, task)): |
| if is_rank_zero: |
| print(cyan("Executing task:"), f"{task} out of {self.cfg.tasks}") |
| getattr(self, task)() |
| else: |
| raise ValueError( |
| f"Specified task '{task}' not defined for class {self.__class__.__name__} or is not callable." |
| ) |
|
|
| def exec_interactive(self, task: str) -> None: |
| """ |
| Executing a certain task specified by string. Each task should be a stage of experiment. |
| In most computer vision / nlp applications, tasks should be just train and test. |
| In reinforcement learning, you might have more stages such as collecting dataset etc |
| |
| Args: |
| task: a string specifying a task implemented for this experiment |
| """ |
| if hasattr(self, task) and callable(getattr(self, task)): |
| if is_rank_zero: |
| print(cyan("Executing task:"), f"{task} out of {self.cfg.tasks}") |
| return getattr(self, task)() |
| else: |
| raise ValueError( |
| f"Specified task '{task}' not defined for class {self.__class__.__name__} or is not callable." |
| ) |
|
|
| class BaseLightningExperiment(BaseExperiment): |
| """ |
| Abstract class for pytorch lightning experiments. Useful for computer vision & nlp where main components are |
| simply models, datasets and train loop. |
| """ |
|
|
| |
| compatible_algorithms: Dict = NotImplementedError |
|
|
| |
| compatible_datasets: Dict = NotImplementedError |
|
|
| def _build_trainer_callbacks(self): |
| callbacks = [] |
| if self.logger: |
| callbacks.append(LearningRateMonitor("step", True)) |
| if "checkpointing" in self.cfg.training: |
| checkpointing_cfg = dict(self.cfg.training.checkpointing) |
| save_last_k = checkpointing_cfg.pop("save_last_k", None) |
| checkpoint_cls = LastKStepModelCheckpoint if save_last_k is not None else ModelCheckpoint |
| if save_last_k is not None: |
| checkpointing_cfg["save_last_k"] = save_last_k |
| callbacks.append( |
| checkpoint_cls( |
| pathlib.Path(hydra.core.hydra_config.HydraConfig.get()["runtime"]["output_dir"]) / "checkpoints", |
| filename="epoch{epoch}_step{step}", |
| auto_insert_metric_name=False, |
| **checkpointing_cfg, |
| ) |
| ) |
| return callbacks |
|
|
| def _build_training_trainer(self, max_steps: int): |
| return pl.Trainer( |
| accelerator="auto", |
| devices="auto", |
| strategy=DDPStrategy(find_unused_parameters=True) if torch.cuda.device_count() > 1 else "auto", |
| logger=self.logger or False, |
| callbacks=self._build_trainer_callbacks(), |
| gradient_clip_val=self.cfg.training.optim.gradient_clip_val or 0.0, |
| val_check_interval=self.cfg.validation.val_every_n_step if self.cfg.validation.val_every_n_step else None, |
| limit_val_batches=self.cfg.validation.limit_batch, |
| check_val_every_n_epoch=self.cfg.validation.val_every_n_epoch if not self.cfg.validation.val_every_n_step else None, |
| accumulate_grad_batches=self.cfg.training.optim.accumulate_grad_batches or 1, |
| precision=self.cfg.training.precision or 32, |
| detect_anomaly=False, |
| num_sanity_val_steps=int(self.cfg.debug) if self.cfg.debug else 0, |
| max_epochs=self.cfg.training.max_epochs, |
| max_steps=max_steps, |
| max_time=self.cfg.training.max_time |
| ) |
|
|
| def _curriculum_stages(self): |
| curriculum = self.cfg.training.get("curriculum", None) |
| if curriculum is None or not bool(curriculum.get("enabled", True)): |
| return None |
| stages = curriculum.get("stages", None) |
| return stages if stages is not None and len(stages) > 0 else None |
|
|
| def _merge_curriculum_updates(self, target_cfg: DictConfig, updates: DictConfig) -> None: |
| with open_dict(target_cfg): |
| for key, value in updates.items(): |
| if key in target_cfg and isinstance(target_cfg[key], DictConfig) and isinstance(value, DictConfig): |
| self._merge_curriculum_updates(target_cfg[key], value) |
| else: |
| target_cfg[key] = value |
|
|
| def _apply_curriculum_stage(self, stage: DictConfig, stage_idx: int, until_step: int) -> None: |
| for cfg_name in ("dataset", "algorithm"): |
| updates = stage.get(cfg_name, None) |
| if updates is not None: |
| self._merge_curriculum_updates(self.root_cfg[cfg_name], updates) |
| if is_rank_zero: |
| print(cyan("Curriculum stage:"), f"{stage.get('name', stage_idx + 1)} until step {until_step}") |
|
|
| def _curriculum_until_step(self, stage: DictConfig, stage_idx: int) -> int: |
| if "until_step" not in stage: |
| raise ValueError(f"experiment.training.curriculum.stages[{stage_idx}] must define until_step") |
| return int(stage.until_step) |
|
|
| def _curriculum_checkpoint_path(self, stage: DictConfig, stage_idx: int, until_step: int) -> pathlib.Path: |
| checkpoint_dir = pathlib.Path(hydra.core.hydra_config.HydraConfig.get()["runtime"]["output_dir"]) / "checkpoints" |
| stage_name = str(stage.get("name", f"stage_{stage_idx + 1}")) |
| safe_name = "".join(ch if ch.isalnum() or ch in "._-" else "_" for ch in stage_name) |
| return checkpoint_dir / f"curriculum_{stage_idx + 1:02d}_{safe_name}_step{until_step}.ckpt" |
|
|
| def _prepare_training_weights(self): |
| if self.auto_resuming: |
| return self.ckpt_path |
| if self.customized_load: |
| self._load_custom_checkpoint_for_task() |
|
|
| if self.zero_init_gate: |
| for name, para in self.algo.diffusion_model.named_parameters(): |
| if 'r_adaLN_modulation' in name: |
| para.requires_grad_(False) |
| para[2*1024:3*1024] = 0 |
| para[5*1024:6*1024] = 0 |
| para.requires_grad_(True) |
|
|
| if self.only_tune_memory: |
| for name, para in self.algo.diffusion_model.named_parameters(): |
| para.requires_grad_(False) |
| if 'r_' in name or 'pose_embedder' in name or 'pose_cond_mlp' in name or 'lora_' in name: |
| para.requires_grad_(True) |
|
|
| return None if self.customized_load else self.ckpt_path |
|
|
| def _fit_training_curriculum(self, stages, ckpt_path) -> None: |
| resume_step = _checkpoint_step_from_path(ckpt_path) if self.auto_resuming else None |
| for stage_idx, stage in enumerate(stages): |
| until_step = self._curriculum_until_step(stage, stage_idx) |
| self._apply_curriculum_stage(stage, stage_idx, until_step) |
| if resume_step is not None and resume_step >= until_step: |
| if is_rank_zero: |
| print( |
| cyan("Skipping curriculum stage:"), |
| f"{stage.get('name', stage_idx + 1)} until step {until_step}; " |
| f"resume checkpoint is already at step {resume_step}", |
| ) |
| continue |
| trainer = self._build_training_trainer(max_steps=until_step) |
| trainer.fit( |
| self.algo, |
| train_dataloaders=self._build_training_loader(), |
| val_dataloaders=self._build_validation_loader(), |
| ckpt_path=ckpt_path, |
| ) |
| if stage_idx + 1 < len(stages): |
| ckpt_path = self._curriculum_checkpoint_path(stage, stage_idx, until_step) |
| ckpt_path.parent.mkdir(parents=True, exist_ok=True) |
| trainer.save_checkpoint(ckpt_path) |
|
|
| def _build_training_loader(self) -> Optional[Union[TRAIN_DATALOADERS, pl.LightningDataModule]]: |
| train_dataset = self._build_dataset("training") |
| shuffle = ( |
| False if isinstance(train_dataset, torch.utils.data.IterableDataset) else self.cfg.training.data.shuffle |
| ) |
| if train_dataset: |
| return torch.utils.data.DataLoader( |
| train_dataset, |
| batch_size=self.cfg.training.batch_size, |
| num_workers=min(os.cpu_count(), self.cfg.training.data.num_workers), |
| shuffle=shuffle, |
| persistent_workers=True, |
| ) |
| else: |
| return None |
|
|
| def _build_validation_loader(self) -> Optional[Union[TRAIN_DATALOADERS, pl.LightningDataModule]]: |
| validation_dataset = self._build_dataset("validation") |
| shuffle = ( |
| False |
| if isinstance(validation_dataset, torch.utils.data.IterableDataset) |
| else self.cfg.validation.data.shuffle |
| ) |
| if validation_dataset: |
| return torch.utils.data.DataLoader( |
| validation_dataset, |
| batch_size=self.cfg.validation.batch_size, |
| num_workers=min(os.cpu_count(), self.cfg.validation.data.num_workers), |
| shuffle=shuffle, |
| persistent_workers=True, |
| ) |
| else: |
| return None |
|
|
| def _build_test_loader(self) -> Optional[Union[TRAIN_DATALOADERS, pl.LightningDataModule]]: |
| test_dataset = self._build_dataset("test") |
| shuffle = False if isinstance(test_dataset, torch.utils.data.IterableDataset) else self.cfg.test.data.shuffle |
| if test_dataset: |
| return torch.utils.data.DataLoader( |
| test_dataset, |
| batch_size=self.cfg.test.batch_size, |
| num_workers=min(os.cpu_count(), self.cfg.test.data.num_workers), |
| shuffle=shuffle, |
| persistent_workers=True, |
| ) |
| else: |
| return None |
|
|
| def training(self) -> None: |
| """ |
| All training happens here |
| """ |
| if not self.algo: |
| self.algo = self._build_algo() |
| if self.cfg.training.compile: |
| self.algo = torch.compile(self.algo) |
|
|
| ckpt_path = self._prepare_training_weights() |
| curriculum_stages = self._curriculum_stages() |
| if curriculum_stages is not None: |
| self._fit_training_curriculum(curriculum_stages, ckpt_path) |
| return |
|
|
| trainer = self._build_training_trainer(max_steps=self.cfg.training.max_steps) |
| trainer.fit( |
| self.algo, |
| train_dataloaders=self._build_training_loader(), |
| val_dataloaders=self._build_validation_loader(), |
| ckpt_path=ckpt_path, |
| ) |
|
|
| def validation(self) -> None: |
| """ |
| All validation happens here |
| """ |
| if not self.algo: |
| self.algo = self._build_algo() |
| if self.cfg.validation.compile: |
| self.algo = torch.compile(self.algo) |
|
|
| callbacks = [] |
|
|
| trainer = pl.Trainer( |
| accelerator="auto", |
| logger=self.logger, |
| devices="auto", |
| num_nodes=self.cfg.num_nodes, |
| strategy=DDPStrategy(find_unused_parameters=False, timeout=timedelta(hours=1)) if torch.cuda.device_count() > 1 else "auto", |
| callbacks=callbacks, |
| limit_val_batches=self.cfg.validation.limit_batch, |
| precision=self.cfg.validation.precision, |
| detect_anomaly=False, |
| inference_mode=self.cfg.validation.inference_mode, |
| ) |
|
|
| if self.customized_load: |
| self._load_custom_checkpoint_for_task() |
|
|
| if self.zero_init_gate: |
| for name, para in self.algo.diffusion_model.named_parameters(): |
| if 'r_adaLN_modulation' in name: |
| para.requires_grad_(False) |
| para[2*1024:3*1024] = 0 |
| para[5*1024:6*1024] = 0 |
| para.requires_grad_(True) |
| |
| trainer.validate( |
| self.algo, |
| dataloaders=self._build_validation_loader(), |
| ckpt_path=None, |
| ) |
| else: |
| trainer.validate( |
| self.algo, |
| dataloaders=self._build_validation_loader(), |
| ckpt_path=self.ckpt_path, |
| ) |
|
|
| def test(self) -> None: |
| """ |
| All testing happens here |
| """ |
| if not self.algo: |
| self.algo = self._build_algo() |
| if self.cfg.test.compile: |
| self.algo = torch.compile(self.algo) |
|
|
| callbacks = [] |
|
|
| trainer = pl.Trainer( |
| accelerator="auto", |
| logger=self.logger, |
| devices="auto", |
| num_nodes=self.cfg.num_nodes, |
| strategy=DDPStrategy(find_unused_parameters=False, timeout=timedelta(hours=1)) if torch.cuda.device_count() > 1 else "auto", |
| callbacks=callbacks, |
| limit_test_batches=self.cfg.test.limit_batch, |
| precision=self.cfg.test.precision, |
| inference_mode=self.cfg.test.inference_mode, |
| detect_anomaly=False, |
| ) |
|
|
| if self.customized_load: |
| self._load_custom_checkpoint_for_task() |
|
|
| if self.zero_init_gate: |
| for name, para in self.algo.diffusion_model.named_parameters(): |
| if 'r_adaLN_modulation' in name: |
| para.requires_grad_(False) |
| para[2*1024:3*1024] = 0 |
| para[5*1024:6*1024] = 0 |
| para.requires_grad_(True) |
| |
| trainer.test( |
| self.algo, |
| dataloaders=self._build_test_loader(), |
| ckpt_path=None, |
| ) |
| else: |
| trainer.test( |
| self.algo, |
| dataloaders=self._build_test_loader(), |
| ckpt_path=self.ckpt_path, |
| ) |
|
|
|
|
| if not self.algo: |
| self.algo = self._build_algo() |
| if self.cfg.validation.compile: |
| self.algo = torch.compile(self.algo) |
|
|
| def _build_dataset(self, split: str) -> Optional[torch.utils.data.Dataset]: |
| if split in ["training", "test", "validation"]: |
| return self.compatible_datasets[self.root_cfg.dataset._name](self.root_cfg.dataset, split=split) |
| else: |
| raise NotImplementedError(f"split '{split}' is not implemented") |
|
|