|
|
|
|
|
|
|
|
|
|
|
""" |
|
|
Finite Scalar Quantization: VQ-VAE Made Simple - https://arxiv.org/abs/2309.15505 |
|
|
Code adapted from Jax version in Appendix A.1 |
|
|
""" |
|
|
|
|
|
from typing import List, Optional |
|
|
|
|
|
import torch |
|
|
import torch.nn as nn |
|
|
from torch.nn import Module |
|
|
from torch import Tensor, int32 |
|
|
from torch.cuda.amp import autocast |
|
|
|
|
|
from einops import rearrange, pack, unpack |
|
|
|
|
|
|
|
|
|
|
|
def exists(v): |
|
|
return v is not None |
|
|
|
|
|
def default(*args): |
|
|
for arg in args: |
|
|
if exists(arg): |
|
|
return arg |
|
|
return None |
|
|
|
|
|
def pack_one(t, pattern): |
|
|
return pack([t], pattern) |
|
|
|
|
|
def unpack_one(t, ps, pattern): |
|
|
return unpack(t, ps, pattern)[0] |
|
|
|
|
|
|
|
|
|
|
|
def round_ste(z: Tensor) -> Tensor: |
|
|
"""Round with straight through gradients.""" |
|
|
zhat = z.round() |
|
|
return z + (zhat - z).detach() |
|
|
|
|
|
|
|
|
|
|
|
class FSQ(Module): |
|
|
def __init__( |
|
|
self, |
|
|
num_lvl: int, |
|
|
|
|
|
dim: Optional[int] = None, |
|
|
num_codebooks = 1, |
|
|
keep_num_codebooks_dim: Optional[bool] = None, |
|
|
scale: Optional[float] = None |
|
|
): |
|
|
super().__init__() |
|
|
levels = [num_lvl] * dim |
|
|
_levels = torch.tensor(levels, dtype=int32) |
|
|
self.register_buffer("_levels", _levels, persistent = False) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.scale = scale |
|
|
|
|
|
codebook_dim = len(levels) |
|
|
self.codebook_dim = codebook_dim |
|
|
|
|
|
effective_codebook_dim = codebook_dim * num_codebooks |
|
|
self.num_codebooks = num_codebooks |
|
|
self.effective_codebook_dim = effective_codebook_dim |
|
|
|
|
|
keep_num_codebooks_dim = default(keep_num_codebooks_dim, num_codebooks > 1) |
|
|
assert not (num_codebooks > 1 and not keep_num_codebooks_dim) |
|
|
self.keep_num_codebooks_dim = keep_num_codebooks_dim |
|
|
|
|
|
self.dim = default(dim, len(_levels) * num_codebooks) |
|
|
|
|
|
has_projections = self.dim != effective_codebook_dim |
|
|
self.project_in = nn.Linear(self.dim, effective_codebook_dim) if has_projections else nn.Identity() |
|
|
self.project_out = nn.Linear(effective_codebook_dim, self.dim) if has_projections else nn.Identity() |
|
|
self.has_projections = has_projections |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def bound(self, z: Tensor, eps: float = 1e-3) -> Tensor: |
|
|
"""Bound `z`, an array of shape (..., d).""" |
|
|
half_l = (self._levels - 1) * (1 - eps) / 2 |
|
|
offset = torch.where(self._levels % 2 == 0, 0.5, 0.0) |
|
|
shift = (offset / half_l).tan() |
|
|
return (z + shift).tanh() * half_l - offset |
|
|
|
|
|
def quantize(self, z: Tensor) -> Tensor: |
|
|
"""Quantizes z, returns quantized zhat, same shape as z.""" |
|
|
quantized = round_ste(self.bound(z)) |
|
|
half_width = self._levels // 2 |
|
|
return quantized / half_width |
|
|
|
|
|
def _scale_and_shift(self, zhat_normalized: Tensor) -> Tensor: |
|
|
half_width = self._levels // 2 |
|
|
return (zhat_normalized * half_width) + half_width |
|
|
|
|
|
def _scale_and_shift_inverse(self, zhat: Tensor) -> Tensor: |
|
|
half_width = self._levels // 2 |
|
|
return (zhat - half_width) / half_width |
|
|
|
|
|
def codes_to_indices(self, zhat: Tensor) -> Tensor: |
|
|
"""Converts a `code` to an index in the codebook.""" |
|
|
assert zhat.shape[-1] == self.codebook_dim |
|
|
zhat = self._scale_and_shift(zhat) |
|
|
|
|
|
return zhat.to(int32) |
|
|
|
|
|
def indices_to_codes( |
|
|
self, |
|
|
indices: Tensor, |
|
|
project_out = True, |
|
|
**kwargs, |
|
|
) -> Tensor: |
|
|
"""Inverse of `codes_to_indices`.""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
codes = self._scale_and_shift_inverse(indices) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if project_out: |
|
|
codes = self.project_out(codes) |
|
|
|
|
|
|
|
|
codes = rearrange(codes, 'b ... d -> b d ...') |
|
|
|
|
|
return codes |
|
|
|
|
|
@autocast(enabled = False) |
|
|
def forward(self, z: Tensor) -> Tensor: |
|
|
""" |
|
|
einstein notation |
|
|
b - batch |
|
|
n - sequence (or flattened spatial dimensions) |
|
|
d - feature dimension |
|
|
c - number of codebook dim |
|
|
""" |
|
|
|
|
|
is_img_or_video = z.ndim >= 4 |
|
|
|
|
|
|
|
|
|
|
|
if is_img_or_video: |
|
|
z = rearrange(z, 'b d ... -> b ... d') |
|
|
|
|
|
|
|
|
assert z.shape[-1] == self.dim, f'expected dimension of {self.dim} but found dimension of {z.shape[-1]}' |
|
|
|
|
|
z = self.project_in(z) |
|
|
|
|
|
|
|
|
|
|
|
codes = self.quantize(z) |
|
|
indices = self.codes_to_indices(codes) |
|
|
|
|
|
|
|
|
|
|
|
out = self.project_out(codes) |
|
|
|
|
|
|
|
|
|
|
|
if is_img_or_video: |
|
|
|
|
|
out = rearrange(out, 'b ... d -> b d ...') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return out, None, indices, None |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
num_lvl = 5 |
|
|
dim = 16 |
|
|
T, H, W = 21, 32, 32 |
|
|
quantizer = FSQ(num_lvl, dim) |
|
|
z = torch.randn(2, dim, T, H, W) |
|
|
out, indices = quantizer(z) |