akashch1512's picture
Add DINOv3 model components and loss functions
40d7c69
Raw History Blame Contribute Delete
3.24 kB
"""Exponential moving average of the trainable weights.
Cheap, reliably worth 2-5 % RMSE on a cosine-annealed run, and it makes the final
checkpoint insensitive to which exact step training happened to stop on — which
matters here because the run is wall-clock capped and can be cut mid-epoch.
"""
from __future__ import annotations
import torch
from torch import nn
class ModelEMA:
def __init__(self, model: nn.Module, decay: float = 0.9995):
self.decay = float(decay)
self.shadow = {k: v.detach().clone().float()
for k, v in model.state_dict().items()
if v.dtype.is_floating_point}
self.n = 0
self._pairs: list | None = None # (shadow, live) lists, built lazily
def _build_pairs(self, model: nn.Module):
"""Pair shadow and live tensors once so `update` is two fused kernels.
The per-key Python loop launched ~800 kernels a step on a ViT-L and
allocated a full fp32 copy of every parameter to do it; `_foreach_*`
does the same arithmetic in two calls with no temporaries.
"""
sd = model.state_dict()
sh, live = [], []
for k, s in self.shadow.items():
v = sd.get(k)
if v is not None and v.shape == s.shape and v.dtype == s.dtype:
sh.append(s)
live.append(v)
self._pairs = [sh, live]
return self._pairs
@torch.no_grad()
def update(self, model: nn.Module) -> None:
self.n += 1
# warm up the decay so the first steps aren't dominated by the init
d = min(self.decay, (1 + self.n) / (10 + self.n))
pairs = self._pairs or self._build_pairs(model)
sh, live = pairs
if len(sh) == len(self.shadow):
# fast path: every shadow tensor matches a live tensor exactly
torch._foreach_mul_(sh, d)
torch._foreach_add_(sh, live, alpha=1.0 - d)
return
for k, v in model.state_dict().items():
s = self.shadow.get(k)
if s is None:
continue
if s.shape != v.shape: # a param group changed shape
self.shadow[k] = v.detach().clone().float()
self._pairs = None
continue
s.mul_(d).add_(v.detach().float(), alpha=1.0 - d)
def state_dict(self) -> dict:
return {k: v.clone() for k, v in self.shadow.items()}
class swapped:
"""`with ema.swapped(model): ...` — evaluate on the EMA weights."""
def __init__(self, ema: "ModelEMA", model: nn.Module):
self.ema, self.model = ema, model
self.backup: dict = {}
def __enter__(self):
sd = self.model.state_dict()
for k, v in self.ema.shadow.items():
if k in sd and sd[k].shape == v.shape:
self.backup[k] = sd[k].detach().clone()
sd[k].copy_(v.to(sd[k].dtype))
return self.model
def __exit__(self, *exc):
sd = self.model.state_dict()
for k, v in self.backup.items():
sd[k].copy_(v)
self.backup.clear()
return False