Source code for pretrain.models.nemo.nemo_pretrain

import multiprocessing as mp

import hydra
import torch
import torch.distributed as dist
from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR, SequentialLR
from torch.utils.data import DataLoader, RandomSampler

from core.dataset import WholeSessionSpikeDataset
from core.nn.loss import CLIPLoss
from core.samplers import DistributedSamplerWrapper
from core.trainer import BaseTrainer
from core.utils.util import get_num_params, get_num_trainable_params, human_readable

from .augmentations import get_acg_transform, get_wf_transform
from .cache import NEMO_UNIT_QC
from .dataset import NEMOAugmentedTensorDataset
from .encoding import encode, load_units, preprocess
from .monitor import BrainRegionMonitor
from .nemo import NEMO


[docs] class NEMOPretrain(BaseTrainer): """Trainer for NEMO SSL pretraining (CLIP-style contrastive learning)."""
[docs] def setup(self, ckpt: dict | None): self.logger.info(f"Trainer: {self.__class__}") self._setup_data() self.setup_model() self.setup_train_loader() self.setup_optimizer(ckpt) if self.rank == 0: self.monitor: BrainRegionMonitor | None = None if self.cfg.monitor is not None: self.monitor = hydra.utils.instantiate(self.cfg.monitor, world_size=self.world_size)
[docs] def train_epoch(self): for wf_batch, acg_batch in self.logger.get_pbar(self.train_loader, prefix="train"): wf_batch, acg_batch = preprocess(wf_batch, acg_batch, self.device) self.optimizer.zero_grad() z_wf, z_acg = self.model(wf_batch, acg_batch.unsqueeze(1)) loss = self.loss_fn(z_wf, z_acg) loss.backward() self.clip_and_log_grad_norm() self.optimizer.step() self.lr_scheduler.step() self.logger.log("train/loss", loss.item(), pbar=True) self.train_step += 1 self.push_logs()
[docs] @torch.inference_mode() def val_epoch(self): self.model.eval() # no monitor means no score, hence no best-model selection and no early stopping early_stop = False if self.rank == 0 and self.monitor is not None: train_embs = encode( self.model, self.train_waveforms, self.train_acgs, self.cfg.batch_size, self.device, self.precision.dtype, self.logger, ) metrics = self.monitor(train_embs=train_embs, train_uids=self.train_uids) self.logger.log_dict(metrics) self.return_value = {**metrics, "val/epoch": self.epoch} self.logger.log("val/epoch", self.epoch) improved = self.save_if_best(metrics[self.cfg.val.metric]) early_stop = self.step_patience(improved) self.push_logs() # only rank 0 scores, but every rank must leave the training loop together if self.is_distributed: flag = torch.tensor(int(early_stop), device=self.device) dist.broadcast(flag, src=0) early_stop = bool(flag.item()) return early_stop
def _setup_data(self): dataset = WholeSessionSpikeDataset( root=self.cfg.data_root, regime="pretrain", unit_qc=NEMO_UNIT_QC ) self.train_waveforms, self.train_acgs, self.train_uids = load_units( self.cfg.cache_path, dataset ) def setup_model(self): self.model = hydra.utils.instantiate(self.cfg.model) if not isinstance(self.model, NEMO): raise TypeError( f"{type(self).__name__} requires a NEMO model, got {type(self.model).__name__}" ) self.add_checkpoint_items(model=self.model) self.loss_fn = CLIPLoss(temperature=self.cfg.loss.temperature) self.logger.info(f"Precision: {self.precision}") self.logger.info(f"Model: {self.model.__class__}") self.model.train() num_params, skipped_params = get_num_params(self.model) num_params_acg, _ = get_num_params(self.model.acg_encoder) num_params_wvf, _ = get_num_params(self.model.wvf_encoder) num_params_acg_proj, _ = get_num_params(self.model.acg_projector) num_params_wvf_proj, _ = get_num_params(self.model.wvf_projector) num_trainable_params = get_num_trainable_params(self.model, return_skipped=False) self.logger.info(f"Number of parameters: {human_readable(num_params)} ({num_params:,})") self.logger.info( f"Number of ACG encoder params: {human_readable(num_params_acg)} ({num_params_acg:,})" ) self.logger.info( f"Number of wvf encoder params: {human_readable(num_params_wvf)} ({num_params_wvf:,})" ) self.logger.info( f"Number of ACG projector params: {human_readable(num_params_acg_proj)} ({num_params_acg_proj:,})" ) self.logger.info( f"Number of wvf projector params: {human_readable(num_params_wvf_proj)} ({num_params_wvf_proj:,})" ) self.logger.info( f"Number of trainable parameters: {human_readable(num_trainable_params)} ({num_trainable_params:,})" ) if skipped_params: self.logger.info(f"Skipped lazy parameters: {skipped_params}") self.model = self.make_ddp(self.model.to(self.device)) def setup_train_loader(self): has_workers = self.cfg.num_workers > 0 dataset = NEMOAugmentedTensorDataset( self.train_waveforms, self.train_acgs, wvf_transform=get_wf_transform(), acg_transform=get_acg_transform(), ) self.train_sampler = DistributedSamplerWrapper( RandomSampler(dataset, generator=torch.Generator().manual_seed(self.cfg.seed)) ) self.train_loader = DataLoader( dataset, sampler=self.train_sampler, batch_size=self.cfg.batch_size // self.world_size, num_workers=self.cfg.num_workers, pin_memory=self.cfg.pin_memory, prefetch_factor=self.cfg.prefetch_factor if has_workers else None, persistent_workers=self.cfg.persistent_workers if has_workers else False, multiprocessing_context=mp.get_context("fork") if has_workers else None, ) def setup_optimizer(self, ckpt: dict | None): if self.model is None: raise ValueError("Trying to set up optimizers before the model is setup") if self.train_loader is None: raise ValueError("Trying to set up optimizers before the train data loader is setup") grouped_parameters = self.get_param_groups() self.optimizer = torch.optim.AdamW( grouped_parameters, lr=self.cfg.base_lr, ) steps_per_epoch = len(self.train_loader) warmup_steps = 1 * steps_per_epoch total_steps = steps_per_epoch * self.cfg.num_epochs s1 = LinearLR(self.optimizer, start_factor=0.01, total_iters=warmup_steps) s2 = CosineAnnealingLR( self.optimizer, T_max=total_steps - warmup_steps, eta_min=self.cfg.base_lr * self.cfg.lr_decay, ) self.lr_scheduler = SequentialLR( self.optimizer, schedulers=[s1, s2], milestones=[warmup_steps], ) self.logger.info(f"LR Scheduler: {self.lr_scheduler}") self.add_checkpoint_items( optimizer=self.optimizer, lr_scheduler=self.lr_scheduler, )