anilegin's picture
Upload inference-only latent diffusion model
a04730e verified
Raw
History Blame Contribute Delete
5.68 kB
from __future__ import annotations
import torch
from torch import nn
from src.models.autoencoder.encoder import Encoder
from src.models.autoencoder.decoder import Decoder
from src.models.autoencoder.distributions import DiagonalGaussianDistribution
class AutoencoderKL(nn.Module):
"""
VAE / AutoencoderKL wrapper
posterior = vae.encode(x)
z = posterior.sample()
x_recon = vae.decode(z)
Or directly:
x_recon, posterior, z = vae(x)
Input image range:
x in [-1, 1]
Output image range:
x_recon in [-1, 1]
"""
def __init__(
self,
in_channels: int = 3,
out_channels: int = 3,
latent_channels: int = 8,
base_channels: int = 128,
channel_multipliers: list[int] | tuple[int, ...] = (1, 2, 4, 4),
num_res_blocks: int = 3,
dropout: float = 0.0,
use_attention: bool = True,
attention_heads: int = 4,
scaling_factor: float = 1.0,
attention_resolutions: tuple[int, ...] = (32,),
):
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.latent_channels = latent_channels
self.base_channels = base_channels
self.channel_multipliers = list(channel_multipliers)
self.num_res_blocks = num_res_blocks
self.scaling_factor = scaling_factor
self.encoder = Encoder(
in_channels=in_channels,
latent_channels=latent_channels,
base_channels=base_channels,
channel_multipliers=channel_multipliers,
num_res_blocks=num_res_blocks,
dropout=dropout,
use_attention=use_attention,
attention_heads=attention_heads,
attention_resolutions= attention_resolutions
)
self.decoder = Decoder(
out_channels=out_channels,
latent_channels=latent_channels,
base_channels=base_channels,
channel_multipliers=channel_multipliers,
num_res_blocks=num_res_blocks,
dropout=dropout,
use_attention=use_attention,
attention_heads=attention_heads,
)
def encode(
self,
x: torch.Tensor,
deterministic: bool = False,
) -> DiagonalGaussianDistribution:
"""
Encode image into posterior distribution.
deterministic:
If True, posterior.sample() will return mean only.
Returns:
DiagonalGaussianDistribution.
"""
moments = self.encoder(x)
posterior = DiagonalGaussianDistribution(
moments=moments,
deterministic=deterministic,
)
return posterior
def decode(
self,
z: torch.Tensor,
unscale: bool = True,
) -> torch.Tensor:
"""
Decode latent into image
z:
Latent tensor [B, latent_channels, H/8, W/8].
unscale:
If True, divide by scaling_factor before decoding.
Returns:
Reconstructed image in [-1, 1].
"""
if unscale:
z = z / self.scaling_factor
return self.decoder(z)
def forward(
self,
x: torch.Tensor,
sample_posterior: bool = True,
) -> tuple[torch.Tensor, DiagonalGaussianDistribution, torch.Tensor]:
"""
Full VAE forward pass.
Args:
x:
Image tensor [B, 3, H, W], normalized to [-1, 1].
sample_posterior:
If True:
z = posterior.sample()
If False:
z = posterior.mode()
Returns:
x_recon:
Reconstructed image [B, 3, H, W].
posterior:
DiagonalGaussianDistribution object.
z:
Latent tensor before scaling.
"""
posterior = self.encode(x)
if sample_posterior:
z = posterior.sample()
else:
z = posterior.mode()
x_recon = self.decode(z, unscale=False)
return x_recon, posterior, z
@torch.no_grad()
def reconstruct(
self,
x: torch.Tensor,
sample_posterior: bool = False,
) -> torch.Tensor:
"""
Convenience method for inference reconstruction.
By default, uses posterior mode for stable reconstructions.
"""
x_recon, _, _ = self.forward(
x,
sample_posterior=sample_posterior,
)
return x_recon
@torch.no_grad()
def encode_to_latent(
self,
x: torch.Tensor,
sample_posterior: bool = False,
scale: bool = True,
) -> torch.Tensor:
"""
Encode image into latent tensor for latent diffusion training.
Usually for latent caching, use:
sample_posterior=False
because the posterior mean is deterministic and stable.
If scale=True:
z_scaled = z * scaling_factor
Stable Diffusion-style LDMs often scale latents before diffusion.
"""
posterior = self.encode(x)
if sample_posterior:
z = posterior.sample()
else:
z = posterior.mode()
if scale:
z = z * self.scaling_factor
return z
@torch.no_grad()
def decode_from_latent(
self,
z: torch.Tensor,
unscale: bool = True,
) -> torch.Tensor:
"""
Decode latent tensor produced by diffusion model.
"""
return self.decode(z, unscale=unscale)