VOSR / models /qwenimage_vae2d.py
41-807's picture
Upload folder using huggingface_hub
93d565a verified
Raw
History Blame Contribute Delete
11.7 kB
# Copyright 2025
# The Qwen-Image Team, Wan Team and The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# http://www.apache.org/licenses/LICENSE-2.0
from typing import List, Optional, Tuple, Union
import torch
import torch.nn as nn
import torch.nn.functional as F
from diffusers.configuration_utils import ConfigMixin, register_to_config
from diffusers.loaders import FromOriginalModelMixin
from diffusers.models.activations import get_activation
from diffusers.models.modeling_outputs import AutoencoderKLOutput
from diffusers.models.modeling_utils import ModelMixin
from diffusers.models.autoencoders.vae import DecoderOutput, DiagonalGaussianDistribution
class RMSNorm2D(nn.Module):
def __init__(self, dim: int, channel_first: bool = True, bias: bool = False):
super().__init__()
assert channel_first, "Only channel_first=True is supported."
self.channel_first = True
self.scale = dim ** 0.5
shape = (dim, 1, 1)
self.gamma = nn.Parameter(torch.ones(shape))
self.bias = nn.Parameter(torch.zeros(shape)) if bias else 0.0
def forward(self, x):
return F.normalize(x, dim=1) * self.scale * self.gamma + self.bias
class ResidualBlock2D(nn.Module):
def __init__(self, in_dim: int, out_dim: int, dropout: float = 0.0, non_linearity: str = "silu"):
super().__init__()
self.in_dim = in_dim
self.out_dim = out_dim
self.nonlinearity = get_activation(non_linearity)
self.norm1 = RMSNorm2D(in_dim, channel_first=True)
self.conv1 = nn.Conv2d(in_dim, out_dim, kernel_size=3, padding=1)
self.norm2 = RMSNorm2D(out_dim, channel_first=True)
self.dropout = nn.Dropout(dropout)
self.conv2 = nn.Conv2d(out_dim, out_dim, kernel_size=3, padding=1)
self.conv_shortcut = nn.Conv2d(in_dim, out_dim, kernel_size=1) if in_dim != out_dim else nn.Identity()
def forward(self, x):
h = self.conv_shortcut(x)
x = self.norm1(x)
x = self.nonlinearity(x)
x = self.conv1(x)
x = self.norm2(x)
x = self.nonlinearity(x)
x = self.dropout(x)
x = self.conv2(x)
return x + h
class AttentionBlock2D(nn.Module):
def __init__(self, dim: int):
super().__init__()
self.dim = dim
self.norm = RMSNorm2D(dim)
self.to_qkv = nn.Conv2d(dim, dim * 3, kernel_size=1)
self.proj = nn.Conv2d(dim, dim, kernel_size=1)
def forward(self, x):
identity = x
b, c, h, w = x.shape
x = self.norm(x)
qkv = self.to_qkv(x)
qkv = qkv.reshape(b, 3, c, h * w)
qkv = qkv.permute(0, 1, 3, 2).contiguous()
q, k, v = qkv.unbind(dim=1)
x_attn = F.scaled_dot_product_attention(q, k, v)
x_attn = x_attn.squeeze(1)
x_attn = x_attn.permute(0, 2, 1).reshape(b, c, h, w)
x_attn = self.proj(x_attn)
return x_attn + identity
class Resample2D(nn.Module):
def __init__(self, dim: int, mode: str):
super().__init__()
self.dim = dim
self.mode = mode
if mode == "upsample2d":
self.resample = nn.Sequential(
nn.Upsample(scale_factor=2.0, mode="nearest"),
nn.Conv2d(dim, dim // 2, kernel_size=3, padding=1),
)
elif mode == "downsample2d":
self.resample = nn.Sequential(
nn.ZeroPad2d((0, 1, 0, 1)),
nn.Conv2d(dim, dim, kernel_size=3, stride=2),
)
else:
self.resample = nn.Identity()
def forward(self, x):
return self.resample(x)
class MidBlock2D(nn.Module):
def __init__(self, dim: int, dropout: float = 0.0, non_linearity: str = "silu", num_layers: int = 1):
super().__init__()
self.dim = dim
resnets = [ResidualBlock2D(dim, dim, dropout, non_linearity)]
attentions = []
for _ in range(num_layers):
attentions.append(AttentionBlock2D(dim))
resnets.append(ResidualBlock2D(dim, dim, dropout, non_linearity))
self.attentions = nn.ModuleList(attentions)
self.resnets = nn.ModuleList(resnets)
def forward(self, x):
x = self.resnets[0](x)
for attn, resnet in zip(self.attentions, self.resnets[1:]):
if attn is not None:
x = attn(x)
x = resnet(x)
return x
class Encoder2D(nn.Module):
def __init__(
self,
dim=128,
z_dim=4,
dim_mult=[1, 2, 4, 4],
num_res_blocks=2,
attn_scales=[],
dropout=0.0,
non_linearity: str = "silu",
):
super().__init__()
self.dim = dim
self.z_dim = z_dim
self.dim_mult = dim_mult
self.num_res_blocks = num_res_blocks
self.attn_scales = attn_scales
self.nonlinearity = get_activation(non_linearity)
dims = [dim * u for u in [1] + dim_mult]
scale = 1.0
self.conv_in = nn.Conv2d(3, dims[0], kernel_size=3, padding=1)
self.down_blocks = nn.ModuleList([])
for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])):
for _ in range(num_res_blocks):
self.down_blocks.append(ResidualBlock2D(in_dim, out_dim, dropout, non_linearity))
if scale in attn_scales:
self.down_blocks.append(AttentionBlock2D(out_dim))
in_dim = out_dim
if i != len(dim_mult) - 1:
self.down_blocks.append(Resample2D(out_dim, mode="downsample2d"))
scale /= 2.0
self.mid_block = MidBlock2D(out_dim, dropout, non_linearity, num_layers=1)
self.norm_out = RMSNorm2D(out_dim)
self.conv_out = nn.Conv2d(out_dim, z_dim, kernel_size=3, padding=1)
def forward(self, x):
x = self.conv_in(x)
for layer in self.down_blocks:
x = layer(x)
x = self.mid_block(x)
x = self.norm_out(x)
x = self.nonlinearity(x)
x = self.conv_out(x)
return x
class UpBlock2D(nn.Module):
def __init__(self, in_dim: int, out_dim: int, num_res_blocks: int, dropout: float = 0.0, upsample_mode: Optional[str] = None, non_linearity: str = "silu"):
super().__init__()
self.in_dim = in_dim
self.out_dim = out_dim
resnets = []
cur = in_dim
for _ in range(num_res_blocks + 1):
resnets.append(ResidualBlock2D(cur, out_dim, dropout, non_linearity))
cur = out_dim
self.resnets = nn.ModuleList(resnets)
self.upsamplers = None
if upsample_mode is not None:
self.upsamplers = nn.ModuleList([Resample2D(out_dim, mode=upsample_mode)])
def forward(self, x):
for resnet in self.resnets:
x = resnet(x)
if self.upsamplers is not None:
x = self.upsamplers[0](x)
return x
class Decoder2D(nn.Module):
def __init__(
self,
dim=128,
z_dim=4,
dim_mult=[1, 2, 4, 4],
num_res_blocks=2,
attn_scales=[],
dropout=0.0,
non_linearity: str = "silu",
):
super().__init__()
self.dim = dim
self.z_dim = z_dim
self.dim_mult = dim_mult
self.num_res_blocks = num_res_blocks
self.attn_scales = attn_scales
self.nonlinearity = get_activation(non_linearity)
dims = [dim * u for u in [dim_mult[-1]] + dim_mult[::-1]]
self.conv_in = nn.Conv2d(z_dim, dims[0], kernel_size=3, padding=1)
self.mid_block = MidBlock2D(dims[0], dropout, non_linearity, num_layers=1)
self.up_blocks = nn.ModuleList([])
for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])):
if i > 0:
in_dim = in_dim // 2
upsample_mode = None
if i != len(dim_mult) - 1:
upsample_mode = "upsample2d"
up_block = UpBlock2D(in_dim, out_dim, num_res_blocks, dropout, upsample_mode, non_linearity)
self.up_blocks.append(up_block)
self.norm_out = RMSNorm2D(out_dim)
self.conv_out = nn.Conv2d(out_dim, 3, kernel_size=3, padding=1)
def forward(self, x):
x = self.conv_in(x)
x = self.mid_block(x)
for up_block in self.up_blocks:
x = up_block(x)
x = self.norm_out(x)
x = self.nonlinearity(x)
x = self.conv_out(x)
return x
class AutoencoderKLQwenImage2D(ModelMixin, ConfigMixin, FromOriginalModelMixin):
"""
2D Qwen/Wan VAE for image-only use cases.
API: encode(x) -> latent_dist, decode(z) -> sample, forward(sample) -> reconstruct.
"""
_supports_gradient_checkpointing = False
@register_to_config
def __init__(
self,
base_dim: int = 96,
z_dim: int = 16,
dim_mult: Tuple[int] = [1, 2, 4, 4],
num_res_blocks: int = 2,
attn_scales: List[float] = [],
temperal_downsample: List[bool] = [False, True, True],
dropout: float = 0.0,
latents_mean: List[float] = None,
latents_std: List[float] = None,
non_linearity: str = "silu",
) -> None:
super().__init__()
self.z_dim = z_dim
self.temperal_downsample = temperal_downsample
self.spatial_compression_ratio = 2 ** len(self.temperal_downsample)
self.encoder = Encoder2D(
dim=base_dim,
z_dim=z_dim * 2,
dim_mult=list(dim_mult),
num_res_blocks=num_res_blocks,
attn_scales=list(attn_scales),
dropout=dropout,
non_linearity=non_linearity,
)
self.quant_conv = nn.Conv2d(z_dim * 2, z_dim * 2, kernel_size=1)
self.post_quant_conv = nn.Conv2d(z_dim, z_dim, kernel_size=1)
self.decoder = Decoder2D(
dim=base_dim,
z_dim=z_dim,
dim_mult=list(dim_mult),
num_res_blocks=num_res_blocks,
attn_scales=list(attn_scales),
dropout=dropout,
non_linearity=non_linearity,
)
if latents_mean is None:
latents_mean = [-0.7571, -0.7089, -0.9113, 0.1075, -0.1745, 0.9653, -0.1517, 1.5508, 0.4134, -0.0715, 0.5517, -0.3632, -0.1922, -0.9497, 0.2503, -0.2921]
if latents_std is None:
latents_std = [2.8184, 1.4541, 2.3275, 2.6558, 1.2196, 1.7708, 2.6052, 2.0743, 3.2687, 2.1526, 2.8652, 1.5579, 1.6382, 1.1253, 2.8251, 1.9160]
self.latents_mean = latents_mean
self.latents_std = latents_std
self.use_slicing = False
self.use_tiling = False
def encode(self, x: torch.Tensor, return_dict: bool = True):
h = self.encoder(x)
h = self.quant_conv(h)
posterior = DiagonalGaussianDistribution(h)
if not return_dict:
return (posterior,)
return AutoencoderKLOutput(latent_dist=posterior)
def decode(self, z: torch.Tensor, return_dict: bool = True):
x = self.post_quant_conv(z)
dec = self.decoder(x)
dec = torch.clamp(dec, -1.0, 1.0)
if not return_dict:
return (dec,)
return DecoderOutput(sample=dec)
def forward(
self,
sample: torch.Tensor,
sample_posterior: bool = False,
return_dict: bool = True,
generator: Optional[torch.Generator] = None,
):
posterior = self.encode(sample).latent_dist
z = posterior.sample(generator=generator) if sample_posterior else posterior.mode()
dec = self.decode(z, return_dict=return_dict)
return dec