FlashReFocus / flashrefocus /deblurring /decoder_refiner.py
ming0531's picture
Sync the flashrefocus package
107af9c verified
Raw History Blame Contribute Delete
6.01 kB
"""Decoder refiner (Sec. 3.1, Appendix B.2): the Stable Diffusion 1.5 VAE decoder with fusion modules that inject
features of the blurred input into its up-blocks, recovering structure that VAE decoding loses.
SD1.5 and SD2.1 share the VAE encoder and quant_conv, so the refiner decodes the latents of the SD2.1-based Stage I.
The input enters as (a) the VAE-encoder features at the resolutions of the decoder stages (conv_in, down-blocks 0 and 1,
matched by spatial size) and (b) a guidance map computed from the image and its high-frequency magnitude
(Laplacian-pyramid residual), shared by every fusion module. Each fusion module adds a zero-initialised residual to the
decoder feature, so training starts from the plain decoder. Parameter names follow the released checkpoint."""
import torch
import torch.nn.functional as F
from torch import nn
from torchvision.transforms.functional import gaussian_blur
from .. import paths
from .._compat import patch_huggingface_hub
from ..layers import NAFBlock
patch_huggingface_hub() # before diffusers is imported
def high_frequency_residual(image, kernel_size=5, sigma=1.0):
"""image minus its blurred 2x down-up-sampled version (one Laplacian-pyramid level)."""
low = gaussian_blur(image, kernel_size=kernel_size, sigma=sigma)[:, :, ::2, ::2]
up = gaussian_blur(F.interpolate(low, size=image.shape[-2:], mode="bilinear", align_corners=False),
kernel_size=kernel_size, sigma=sigma)
return image - up
class FeatureFusion(nn.Module):
"""Decoder feature + fusion of (encoder feature, decoder feature, guidance map)."""
def __init__(self, decoder_channels, encoder_channels, guidance_channels, depth):
super().__init__()
self.in_conv = nn.Conv2d(encoder_channels + decoder_channels, decoder_channels, 3, 1, 1)
self.dm_blocks = nn.ModuleList([NAFBlock(decoder_channels + guidance_channels) for _ in range(depth)])
self.out_conv = nn.Conv2d(decoder_channels + guidance_channels, decoder_channels, 3, 1, 1)
def forward(self, encoder_feat, decoder_feat, guidance):
if guidance.shape[-2:] != decoder_feat.shape[-2:]:
guidance = F.interpolate(guidance, size=decoder_feat.shape[-2:], mode="bilinear", align_corners=False)
fused = torch.cat((self.in_conv(torch.cat((encoder_feat, decoder_feat), dim=1)), guidance), dim=1)
for block in self.dm_blocks:
fused = block(fused)
return self.out_conv(fused) + decoder_feat
def _last_resnet_channels(block):
return int(block.resnets[-1].out_channels)
class DecoderRefiner(nn.Module):
ENCODER_STAGES = 2 # down-blocks whose features the decoder can match (the decoder runs at H/4, H/2, H, H)
def __init__(self, vae, depth=2, guidance_channels=32):
super().__init__()
self.vae = vae
decoder_channels = [_last_resnet_channels(b) for b in vae.decoder.up_blocks]
encoder_channels = [int(vae.encoder.conv_in.out_channels)] + [_last_resnet_channels(b) for b in vae.encoder.down_blocks]
n = len(decoder_channels)
self.fusion_blocks = nn.ModuleList([
FeatureFusion(dec, encoder_channels[max(0, n - 2 - i)], guidance_channels, depth)
for i, dec in enumerate(decoder_channels)])
self.map_init = nn.Sequential(nn.Conv2d(3 + 1, guidance_channels, 3, 1, 1), # image + high frequency
*[NAFBlock(guidance_channels) for _ in range(depth)])
for block in self.fusion_blocks:
nn.init.zeros_(block.out_conv.weight)
nn.init.zeros_(block.out_conv.bias)
def encoder_features(self, image):
"""VAE-encoder features at the resolutions of the decoder stages: conv_in and the first down-blocks."""
with torch.no_grad():
h = self.vae.encoder.conv_in(image)
feats = [h.detach()]
for block in self.vae.encoder.down_blocks[:self.ENCODER_STAGES]:
h = block(h)
feats.append(h.detach())
return feats
@staticmethod
def _matching_feature(decoder_feat, encoder_features):
for feat in reversed(encoder_features):
if feat.shape[0] == decoder_feat.shape[0] and feat.shape[-2:] == decoder_feat.shape[-2:]:
return feat
return None
def forward(self, image, latent):
"""image: the blurred input in [-1, 1]; latent: the restored latent (unscaled). Returns the image in [-1, 1]."""
encoder_features = self.encoder_features(image)
high_freq = high_frequency_residual(image).abs().mean(dim=1, keepdim=True)
guidance = self.map_init(torch.cat((image, high_freq), dim=1))
h = self.vae.decoder.mid_block(self.vae.decoder.conv_in(self.vae.post_quant_conv(latent)))
for block, fusion in zip(self.vae.decoder.up_blocks, self.fusion_blocks):
h = block(h)
feat = self._matching_feature(h, encoder_features)
if feat is not None:
h = fusion(feat, h, guidance)
return self.vae.decoder.conv_out(self.vae.decoder.conv_act(self.vae.decoder.conv_norm_out(h)))
def build_decoder_refiner(sd15=paths.SD15):
from diffusers import AutoencoderKL
return DecoderRefiner(AutoencoderKL.from_pretrained(str(sd15), subfolder="vae"))
def load_decoder_refiner(path=paths.REFINER, sd15=paths.SD15, device="cuda", dtype=torch.bfloat16):
"""The released refiner, in bf16 as trained. Its checkpoint stores every parameter except the VAE encoder, which
comes from SD1.5 (only its first two down-blocks run)."""
refiner = build_decoder_refiner(sd15)
res = refiner.load_state_dict(torch.load(str(path), map_location="cpu"), strict=False)
missing = [k for k in res.missing_keys if not k.startswith(("vae.encoder", "vae.quant_conv", "vae.post_quant_conv"))]
assert not missing and not res.unexpected_keys, (missing[:5], res.unexpected_keys[:5])
return refiner.to(device, dtype=dtype).eval()