matteuscruz's picture
Initial HF Space deployment
dcc4594
Raw History Blame Contribute Delete
2.92 kB
# Finite Scalar Quantization from https://arxiv.org/pdf/2309.15505
# quantizes each dimension independently by bounding to 0, num_bins then rounding to nearest integer
# prevents token collapse and no auxiliary losses necessary
import torch
import torch.nn as nn
from einops import rearrange
class FiniteScalarQuantizer(nn.Module):
def __init__(self, latent_dim=5, num_bins=4):
super().__init__()
self.num_bins = num_bins # D
self.levels_np = torch.tensor(latent_dim * [num_bins])
self.codebook_size = num_bins**latent_dim # L^D
# fsq basis [L^0, L^1, ..., L^(L-1)] for converting between indices and latents
self.register_buffer('basis', (num_bins**torch.arange(latent_dim, dtype=torch.long)))
def scale_and_shift(self, z):
# scale and shift z from [-1, 1] to [0, num_bins - 1]
return 0.5 * (z + 1) * (self.num_bins - 1)
def unscale_and_unshift(self, z):
# unscale and unshift z from [0, num_bins - 1] to [-1, 1]
return 2 * z / (self.num_bins - 1) - 1
def forward(self, z):
# z: [B, T, P, L]
# apply 0.5 * (tanh(z) + 1) to go from z range to [0, num_bins - 1]
tanh_z = torch.tanh(z)
bounded_z = self.scale_and_shift(tanh_z)
# round to nearest integer
rounded_z = torch.round(bounded_z)
# stopgrad for straight-through gradient bypassing quantization
quantized_z = bounded_z + (rounded_z - bounded_z).detach()
# normalize back to [-1, 1]
quantized_z = self.unscale_and_unshift(quantized_z)
return quantized_z
def get_codebook_usage(self, quantized_z):
unique_bins = torch.unique(quantized_z).shape[0]
return unique_bins / self.num_bins
def get_indices_from_latents(self, latents, dim=-1):
# to get fsq indices, for each dimension, we get the index and add it (so multiply by L then sum)
# for each dimension of each latent, get sum of (value * L^current_dim) along latent dim which is the index of that latent in the codebook
# codebook size = L^latent_dim
# latents: [*, L]
# go from [-1, 1] to [0, num_bins - 1] in each dimension
digits = torch.round(self.scale_and_shift(latents)).clamp(0, self.num_bins-1)
# get indices for each latent by summing (value * L^current_dim_idx) along latent dim
indices = torch.sum(digits * self.basis.to(latents.device), dim=dim).long() # [*]
return indices
def get_latents_from_indices(self, indices, dim=-1):
# indices: [*]
# recover each entry of latent in range [0, num_bins - 1] by repeatedly dividing by L^current_dim and taking mod
digits = (indices.unsqueeze(-1) // self.basis) % self.num_bins # [*, L]
# go from [0, num_bins - 1] to [-1, 1] in each dimension
latents = self.unscale_and_unshift(digits) # [*, L]
return latents