pengxiang's picture
Add files using upload-large-folder tool
10a0ca0 verified
Raw
History Blame Contribute Delete
11.7 kB
import math
import os
import torch
import torch.nn as nn
from typing import Dict
from ..common import trunc_normal_init_
def shift(input:torch.Tensor, shift, dim=0, fillval=0):
# torch.roll without the copy of the wrap-around section
size = input.size(dim)
fill = torch.full_like(input.narrow(dim, 0, abs(shift)), fillval)
if shift > 0:
output = torch.cat([fill, input.narrow(dim, 0, size-shift)], dim=dim)
if shift < 0:
output = torch.cat([input.narrow(dim, -shift, size+shift), fill], dim=dim)
return output
class WindowMix(nn.Module):
"""Learned convex mix of the last m iterate STATES, used as the damping
anchor inside the fixed-point step. Weights are content-based (query =
current iterate, keys = window states) plus a learnable per-age recency
bias, passed through softmax -> convex (sum=1). Values are the RAW states
(no v/o projection), so the read stays in the convex hull of the window;
at convergence every slot == y* so the mix returns y* -> the fixed point is
preserved exactly. recency_init large + q zero-init => starts one-hot on the
newest state == byte-identical baseline damping. This is learned multi-point
Krasnoselskii-Mann damping (a generalization of FPRM's fixed 2-point blend),
NOT Anderson acceleration (which uses non-convex affine weights)."""
def __init__(self, hidden: int, window: int, recency_init: float = 8.0):
super().__init__()
self.q = nn.Linear(hidden, hidden, bias=False)
self.k = nn.Linear(hidden, hidden, bias=False)
nn.init.zeros_(self.q.weight) # content logit == 0 at init -> pure recency
self.scale = hidden ** -0.5
self.recency = nn.Parameter(-recency_init * torch.arange(window, dtype=torch.float32))
def forward(self, window: torch.Tensor) -> torch.Tensor: # window: [m, B, S, H], index 0 = newest
m = window.shape[0]
cur = window[0]
q = self.q(cur).unsqueeze(0) # [1, B, S, H]
k = self.k(window) # [m, B, S, H]
logit = (q * k).sum(-1) * self.scale # [m, B, S]
logit = logit + self.recency.to(logit.dtype).view(m, 1, 1) # + learned age bias
w = torch.softmax(logit, dim=0) # [m, B, S] convex
return (w.unsqueeze(-1) * window).sum(dim=0).to(window.dtype) # [B, S, H]
class FixedPointOptimizer(nn.Module):
def __init__(self, config: dict):
super().__init__()
self.stepsize = config.stepsize
self.stepsize_decay = config.stepsize_decay
self.decay_patience = config.decay_patience
self.eps = config.eps
self.outlier_quantile = config.outlier_quantile
self.max_iter = config.max_iter
self.init_std = config.init_std
self.additive_noise_std = config.additive_noise_std
self.fp_thresh = config.fp_thresh
# Multi-point convex damping on the loop axis. Window size from config
# (learned mixer, for training) or env (zero-training probe). m<=1 ->
# byte-identical baseline. See loop-attn plan.
cfg_window = int(getattr(config, "loop_window", 0))
env_learned = int(os.environ.get("FP_LEARNED_WINDOW", "0"))
env_uniform = int(os.environ.get("FP_UNIFORM_WINDOW", "0"))
self.loop_window = cfg_window or env_learned or env_uniform
self.window_recency = float(os.environ.get("FP_WINDOW_RECENCY", "0")) # only used by the env probe path
learned = (cfg_window > 1) or (env_learned > 1)
if learned and self.loop_window > 1:
self.window_mix = WindowMix(config.hidden_size, self.loop_window,
float(getattr(config, "loop_recency_init", 8.0)))
else:
self.window_mix = None
self.fixed_init = config.fixed_init
if self.fixed_init:
fwd_dtype = getattr(torch, config.forward_dtype)
self.init_vec = nn.Buffer(
trunc_normal_init_(torch.empty(config.hidden_size, dtype=fwd_dtype), std=self.init_std),
persistent=True,
)
def detach_state(self, state: dict):
for k, v in state.items():
if torch.is_tensor(v):
state[k] = v.detach()
return state
def reset(self, reset_flag: torch.Tensor, shape: tuple, dtype: torch.dtype,
device: torch.device, state: dict, reset_metadata: bool = False):
batch_size, seq_len, hidden_size = shape[0], shape[1], shape[2]
reset_flag_1d = reset_flag.view(-1)
reset_flag_3d = reset_flag_1d.view(-1, 1, 1)
old_hist = state.get('hist') if state is not None else None
if self.fixed_init:
y = self.init_vec.to(dtype=dtype, device=device).expand(batch_size, seq_len, hidden_size).contiguous()
else:
y = trunc_normal_init_(torch.empty(batch_size, seq_len, hidden_size, dtype=dtype, device=device), std=self.init_std)
residues = torch.inf * torch.ones(batch_size).to(device)
stepsize = (self.stepsize * torch.ones(batch_size, 1, 1, dtype=dtype, device=device))
patience = self.decay_patience * torch.ones(batch_size).to(device)
iter_idx = torch.zeros(batch_size, dtype=torch.int32, device=device)
best_residues = torch.inf * torch.ones(batch_size).to(device)
if state is None:
state = dict(y=y.contiguous(),
residues=residues,
stepsize=stepsize,
patience=patience,
iter_idx=iter_idx,
best_residues=best_residues)
else:
state = dict(y=torch.where(reset_flag_3d, y.contiguous(), state['y']),
residues=residues if reset_metadata else torch.where(reset_flag_1d, residues, state['residues']),
stepsize=stepsize if reset_metadata else torch.where(reset_flag_3d, stepsize, state['stepsize']),
patience=patience if reset_metadata else torch.where(reset_flag_1d, patience, state['patience']),
iter_idx=iter_idx if reset_metadata else torch.where(reset_flag_1d, iter_idx, state['iter_idx']),
best_residues=best_residues if reset_metadata else torch.where(reset_flag_1d, best_residues, state['best_residues']))
if self.loop_window > 1:
m = self.loop_window
hist_new = y.detach().unsqueeze(0).repeat(m - 1, 1, 1, 1).contiguous() # [m-1, B, S, H]
if old_hist is None:
state['hist'] = hist_new
else:
state['hist'] = torch.where(reset_flag_3d.unsqueeze(0), hist_new, old_hist)
return state
def step(self, state:Dict[str, torch.Tensor], y:torch.Tensor):
state_dtype = state["y"].dtype
if y.dtype != state_dtype:
y = y.to(state_dtype)
with torch.no_grad():
residues = (state['y'].detach() - y.detach()).norm(p=torch.inf, dim=-1) / (y.detach().norm(p=torch.inf, dim=-1) + self.eps)
residues = residues.max(dim=1)[0]
stepsize = state['stepsize']
if stepsize.dtype != state_dtype:
stepsize = stepsize.to(state_dtype)
if self.loop_window > 1 and 'hist' in state:
prev_y = state['y']
window = torch.cat([prev_y.unsqueeze(0), state['hist']], dim=0) # [m, B, S, H] (index 0 = newest)
if self.window_mix is not None:
blend_anchor = self.window_mix(window) # learned convex mix
else:
m = window.shape[0]
ages = torch.arange(m, device=window.device, dtype=window.dtype) # 0=newest .. m-1=oldest
w = torch.softmax(-self.window_recency * ages, dim=0) # env probe: fixed convex weights
blend_anchor = (w.view(m, 1, 1, 1) * window).sum(dim=0)
state['y'] = y * stepsize + blend_anchor * (1 - stepsize) + self.additive_noise_std * torch.randn_like(y)
state['hist'] = torch.cat([prev_y.unsqueeze(0), state['hist'][:-1]], dim=0) # roll: newest first, drop oldest
else:
state['y'] = y * stepsize + state['y'] * (1 - stepsize) + self.additive_noise_std * torch.randn_like(state['y'])
# Track per-sample best iterate; require improvement of at least 0.01
# so slow monotonic drift doesn't keep resetting patience.
improved = residues < state['best_residues'] - 1e-2
# update patience and lowest residue
state['residues'] = residues
state['best_residues'] = torch.where(improved, residues, state['best_residues'])
state['patience'] = torch.where(improved, self.decay_patience, state['patience']-1)
# update damping factor, reset patience
adapt = (state['patience'] <= 0) & (state['residues'] >= self.fp_thresh)
state['patience'] = torch.where(adapt, self.decay_patience, state['patience'])
stepsize_dtype = state['stepsize'].dtype
# if not self.training:
state['stepsize'] = state['stepsize'] * torch.where(adapt, self.stepsize_decay, 1).to(stepsize_dtype).reshape(-1, 1, 1)
state['iter_idx'] = state['iter_idx'] + 1
return state
def cont(self, state: Dict[str, torch.Tensor], thresh: float):
if int(state['iter_idx'].max().item()) == 0:
return self.max_iter > 0
q = 1 - self.outlier_quantile if self.training else 1.0
return (
(torch.quantile(state['residues'].float(), q=q) >= thresh)
& (torch.quantile(state['iter_idx'].float(), q=q) < self.max_iter)
& (torch.quantile(state['stepsize'].float(), q=q) > 1e-3)
)
class VariationalDropout(nn.Module):
def __init__(self, dropout: float = 0.0):
super().__init__()
assert 0.0 <= dropout < 1.0, f"dropout must be in [0, 1), got {dropout}"
self.dropout = dropout
self._mask: torch.Tensor | None = None
def sample_mask(self, x: torch.Tensor) -> None:
if not self.training or self.dropout == 0.0:
self._mask = None
return
keep_prob = 1.0 - self.dropout
self._mask = torch.bernoulli(torch.full_like(x, keep_prob)) / keep_prob
def forward(self, x: torch.Tensor) -> torch.Tensor:
if not self.training or self.dropout == 0.0 or self._mask is None:
return x
return x * self._mask
class VariationalDropToken1d(nn.Module):
def __init__(self, dropout: float = 0.0, token_first: bool = True):
super().__init__()
assert 0.0 <= dropout < 1.0, f"dropout must be in [0, 1), got {dropout}"
self.dropout = dropout
self.token_first = token_first
self._mask: torch.Tensor | None = None
def sample_mask(self, x: torch.Tensor) -> None:
if not self.training or self.dropout == 0.0:
self._mask = None
return
keep_prob = 1.0 - self.dropout
if self.token_first:
B, L, _ = x.shape
self._mask = torch.bernoulli(torch.full((B, L, 1), keep_prob, device=x.device, dtype=x.dtype)) / keep_prob
else:
B, _, L = x.shape
self._mask = torch.bernoulli(torch.full((B, 1, L), keep_prob, device=x.device, dtype=x.dtype)) / keep_prob
def forward(self, x: torch.Tensor) -> torch.Tensor:
if not self.training or self.dropout == 0.0 or self._mask is None:
return x
return x * self._mask