Download experiments/exp_base.py from BonanDing/DeMemWM: direct link, hf CLI and curl.
- Browser
- Download file 30.6 kB
-
https://huggingface.co/BonanDing/DeMemWM/resolve/main/experiments/exp_base.py
- Command line
-
hf download hf://BonanDing/DeMemWM/experiments/exp_base.py
-
curl -L -o exp_base.py https://huggingface.co/BonanDing/DeMemWM/resolve/main/experiments/exp_base.py
30.6 kB
| """ | |
| 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") | |
| # Lightning needs save-all mode here; this subclass prunes by step after each save. | |
| 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( | |
| # Lightning .ckpt envelopes can contain callback/loop metadata that | |
| # weights_only cannot unpickle; tensors are filtered before loading. | |
| 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)): | |
| # Resolve non-local Hub checkpoint paths before normal state-dict filtering. | |
| 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. | |
| """ | |
| # each key has to be a yaml file under '[project_root]/configurations/algorithm' without .yaml suffix | |
| 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. | |
| """ | |
| # each key has to be a yaml file under '[project_root]/configurations/algorithm' without .yaml suffix | |
| compatible_algorithms: Dict = NotImplementedError | |
| # each key has to be a yaml file under '[project_root]/configurations/dataset' without .yaml suffix | |
| 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, # self.cfg.debug, | |
| 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, # self.cfg.debug, | |
| ) | |
| 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") | |