| from __future__ import annotations |
|
|
| import math |
| from dataclasses import dataclass |
|
|
| import torch |
|
|
|
|
| @dataclass |
| class OptimizerConfig: |
| lr_max: float = 3e-4 |
| lr_min: float = 3e-5 |
| warmup_steps: int = 2000 |
| total_steps: int = 200_000 |
| beta1: float = 0.9 |
| beta2: float = 0.95 |
| eps: float = 1e-8 |
| weight_decay: float = 0.1 |
|
|
|
|
| def create_adamw(model: torch.nn.Module, cfg: OptimizerConfig) -> torch.optim.Optimizer: |
| return torch.optim.AdamW( |
| model.parameters(), |
| lr=cfg.lr_max, |
| betas=(cfg.beta1, cfg.beta2), |
| eps=cfg.eps, |
| weight_decay=cfg.weight_decay, |
| ) |
|
|
|
|
| class CosineWithWarmup(torch.optim.lr_scheduler._LRScheduler): |
| """Warmup -> cosine decay -> floor at lr_min.""" |
|
|
| def __init__( |
| self, |
| optimizer: torch.optim.Optimizer, |
| warmup_steps: int, |
| total_steps: int, |
| lr_max: float, |
| lr_min: float, |
| last_epoch: int = -1, |
| ): |
| self.warmup_steps = max(1, warmup_steps) |
| self.total_steps = max(self.warmup_steps + 1, total_steps) |
| self.lr_max = lr_max |
| self.lr_min = lr_min |
| super().__init__(optimizer, last_epoch) |
|
|
| def get_lr(self): |
| t = self.last_epoch + 1 |
| if t <= self.warmup_steps: |
| lr = self.lr_max * (t / self.warmup_steps) |
| elif t <= self.total_steps: |
| p = (t - self.warmup_steps) / max(1, (self.total_steps - self.warmup_steps)) |
| lr = self.lr_min + 0.5 * (self.lr_max - self.lr_min) * (1.0 + math.cos(math.pi * p)) |
| else: |
| lr = self.lr_min |
| return [lr for _ in self.optimizer.param_groups] |
|
|
|
|
| def create_scheduler( |
| optimizer: torch.optim.Optimizer, |
| cfg: OptimizerConfig, |
| ) -> torch.optim.lr_scheduler._LRScheduler: |
| return CosineWithWarmup( |
| optimizer=optimizer, |
| warmup_steps=cfg.warmup_steps, |
| total_steps=cfg.total_steps, |
| lr_max=cfg.lr_max, |
| lr_min=cfg.lr_min, |
| ) |
|
|