SEDD-medium / graph_lib.py
Jrffy666's picture
Publish audited SEDD-medium math-tool SFT inference weights
f9740ea verified
Raw
History Blame Contribute Delete
7.85 kB
import abc
import torch
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
from torch.cuda.amp import custom_fwd, custom_bwd
from catsample import sample_categorical
def get_graph(config, device):
if config.graph.type == "uniform":
return Uniform(config.tokens)
elif config.graph.type == "absorb":
return Absorbing(config.tokens)
else:
raise ValueError(f"Graph {config.graph.type} not valid")
def unsqueeze_as(x, y, back=True):
if back:
return x.view(*x.shape, *((1,) * (len(y.shape) - len(x.shape))))
else:
return x.view(*((1,) * (len(y.shape) - len(x.shape))), *x.shape)
class Graph(abc.ABC):
@property
def dim(self):
pass
@property
def absorb(self):
"""
Whether input {dim - 1} is an absorbing state (used for denoising to always remove the mask).
"""
pass
@abc.abstractmethod
def rate(self, i):
"""
Computes the i-th column of the rate matrix Q, where i is [B_1, ..., B_n].
This is intended to compute the "forward" rate of p(X_t | X_0 = i).
"""
pass
@abc.abstractmethod
def transp_rate(self, i):
"""
Computes the i-th row of the rate matrix Q.
Can be used to compute the reverse rate.
"""
pass
@abc.abstractmethod
def transition(self, i, sigma):
"""
Computes the i-th column of the transition matrix e^{sigma Q}.
"""
pass
def sample_transition(self, i, sigma):
"""
Samples the transition vector.
"""
transition_vector = self.transition(i, sigma)
return sample_categorical(transition_vector, method="hard")
def reverse_rate(self, i, score):
"""
Constructs the reverse rate. Which is score * transp_rate
"""
normalized_rate = self.transp_rate(i) * score
normalized_rate.scatter_(-1, i[..., None], torch.zeros_like(normalized_rate))
normalized_rate.scatter_(-1, i[..., None], -normalized_rate.sum(dim=-1, keepdim=True))
return normalized_rate
def sample_rate(self, i, rate):
return sample_categorical(F.one_hot(i, num_classes=self.dim).to(rate) + rate)
@abc.abstractmethod
def staggered_score(self, score, dsigma):
"""
Computes p_{sigma - dsigma}(z) / p_{sigma}(x), which is approximated with
e^{-{dsigma} E} score
"""
pass
@abc.abstractmethod
def sample_limit(self, *batch_dims):
"""
Sample the limiting distribution. Returns the probability vector as well.
"""
pass
@abc.abstractmethod
def score_entropy(self, score, sigma, x, x0):
"""
Computes the score entropy function (with requisite constant normalization)
"""
pass
class Uniform(Graph):
"""
Everything goes to everything else. Normalized down by dimension to avoid blowup.
"""
def __init__(self, dim):
self._dim = dim
@property
def dim(self):
return self._dim
@property
def absorb(self):
return False
def rate(self, i):
edge = torch.ones(*i.shape, self.dim, device=i.device) / self.dim
edge = edge.scatter(-1, i[..., None], - (self.dim - 1) / self.dim)
return edge
def transp_rate(self, i):
return self.rate(i)
def transition(self, i, sigma):
trans = torch.ones(*i.shape, self.dim, device=i.device) * (1 - (-sigma[..., None]).exp()) / self.dim
trans = trans.scatter(-1, i[..., None], torch.zeros_like(trans))
trans = trans.scatter(-1, i[..., None], 1 - trans.sum(dim=-1, keepdim=True))
return trans
def transp_transition(self, i, sigma):
return self.transition(i, sigma)
def sample_transition(self, i, sigma):
move_chance = 1 - (-sigma).exp()
move_indices = torch.rand(*i.shape, device=i.device) < move_chance
i_pert = torch.where(move_indices, torch.randint_like(i, self.dim), i)
return i_pert
def staggered_score(self, score, dsigma):
dim = score.shape[-1]
epow = (-dsigma).exp()[..., None]
return ((epow - 1) / (dim * epow)) * score.sum(dim=-1, keepdim=True) + score / epow
def sample_limit(self, *batch_dims):
return torch.randint(0, self.dim, batch_dims)
def score_entropy(self, score, sigma, x, x0):
esigm1 = torch.where(
sigma < 0.5,
torch.expm1(sigma),
torch.exp(sigma) - 1
)
ratio = 1 - self.dim / (esigm1 + self.dim)
# negative term
neg_term = score.mean(dim=-1) - torch.gather(score, -1, x[..., None]).squeeze(-1) / self.dim
# no move means scaling by the uniform ratio. move means alter only one ratio away from 1
neg_term = torch.where(
x == x0,
ratio * neg_term,
torch.gather(score, -1, x0[..., None]).squeeze(-1) / esigm1 + neg_term
)
# constant factor
const = torch.where(
x == x0,
(self.dim - 1) / self.dim * ratio * (ratio.log() - 1),
((-ratio.log() - 1) / ratio - (self.dim - 2)) / self.dim
)
#positive term
sexp = score.exp()
pos_term = sexp.mean(dim=-1) - torch.gather(sexp, -1, x[..., None]).squeeze(-1) / self.dim
return pos_term - neg_term + const
class Absorbing(Graph):
def __init__(self, dim):
super().__init__()
self._dim = dim
@property
def dim(self):
return self._dim + 1
@property
def absorb(self):
return True
def rate(self, i):
# edge = - F.one_hot(i, num_classes=self.dim)
# edge.scatter_add_(-1, i[..., None], torch.ones_like(edge[..., :1]))
return F.one_hot((self.dim - 1) * torch.ones_like(i), num_classes=self.dim) - F.one_hot(i, num_classes=self.dim)
def transp_rate(self, i):
edge = -F.one_hot(i, num_classes=self.dim)
edge[i == self.dim - 1] += 1
return edge
def transition(self, i, sigma):
pass
def transp_transition(self, i, sigma):
sigma = unsqueeze_as(sigma, i[..., None])
edge = (-sigma).exp() * F.one_hot(i, num_classes=self.dim)
edge += torch.where(
i == self.dim - 1,
1 - (-sigma).squeeze(-1).exp(),
0
)[..., None]
return edge
def sample_transition(self, i, sigma):
move_chance = 1 - (-sigma).exp()
move_indices = torch.rand(*i.shape, device=i.device) < move_chance
i_pert = torch.where(move_indices, self.dim - 1, i)
return i_pert
def staggered_score(self, score, dsigma):
score = score.clone() # yeah yeah whatever we should probably do this
extra_const = (1 - (dsigma).exp()) * score.sum(dim=-1)
score *= dsigma.exp()[:, None]
score[..., -1] += extra_const
return score
def sample_limit(self, *batch_dims):
return (self.dim - 1) * torch.ones(*batch_dims, dtype=torch.int64)
def score_entropy(self, score, sigma, x, x0):
rel_ind = x == self.dim - 1
esigm1 = torch.where(
sigma < 0.5,
torch.expm1(sigma),
torch.exp(sigma) - 1
)
ratio = 1 / esigm1.expand_as(x)[rel_ind]
other_ind = x0[rel_ind]
# negative_term
neg_term = ratio * torch.gather(score[rel_ind], -1, other_ind[..., None]).squeeze(-1)
#positive term
pos_term = score[rel_ind][:, :-1].exp().sum(dim=-1)
# constant term
const = ratio * (ratio.log() - 1)
entropy = torch.zeros(*x.shape, device=x.device)
entropy[rel_ind] += pos_term - neg_term + const
return entropy