File size: 7,048 Bytes
076b9c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107af9c
 
 
076b9c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""Stage I one-step diffusion model (Sec. 3.1): Stable Diffusion 2.1 with a pixel LoRA and a detail LoRA, adapted from
PiSA-SR (third_party/PiSA-SR). The restored latent is z_hat = z_blur - eps(z_blur), with eps the U-Net prediction at
timestep 1 and an empty prompt, and

    eps = eps_pix + omega * (eps_pix+det - eps_pix)

where eps_pix uses the pixel LoRA alone, eps_pix+det both LoRAs and omega is the detail weight. omega = 1 is a single
U-Net pass with both LoRAs merged; any other omega needs both predictions (two passes).

With separate_unets=True the two predictions come from two U-Nets (pixel; pixel + detail) instead of one U-Net whose
weights are swapped in place between the passes: twice the U-Net memory, the same output, and every model is an
ordinary module, as needed to move it between devices (e.g. Hugging Face ZeroGPU) or to wrap it.

PiSA-SR names the detail LoRA "sem" (semantic) and omega "lambda_sem"; these names remain in its checkpoint keys."""
import argparse
import copy

import torch

from .. import paths
from .._compat import patch_huggingface_hub

patch_huggingface_hub()   # before diffusers is imported

NO_TILING = 100000   # tile sizes above any input: the whole image is processed at once


def _pisa_sr_eval_class():
    paths.use_third_party(paths.PISA_SR)
    from pisasr import PiSASR_eval
    return PiSASR_eval


class OneStepDiffusion(_pisa_sr_eval_class()):
    """PiSA-SR's evaluation model with the changes needed for deblurring:
      * deterministic VAE encoding (latent_dist.mode()), so the restored image is reproducible;
      * the Stage I extras of our checkpoints (trained U-Net conv_in, LoRA on the VAE encoder);
      * the merged pixel + detail weights of the two-pass mode are built once and reused."""

    def _load_pretrained_weights(self, pretrained_path):
        sd = torch.load(pretrained_path, map_location="cpu")
        if "conv_in" in sd:  # base weights, before the pixel LoRA is merged on top
            self.unet.load_state_dict(sd["conv_in"], strict=False)
        self._load_and_save_ckpt_from_state_dict(sd)
        if "state_dict_vae" in sd:
            from peft import LoraConfig
            self.vae.add_adapter(LoraConfig(r=sd["vae_lora_rank"], init_lora_weights="gaussian",
                                            target_modules=sd["vae_lora_encoder_modules"]), adapter_name="vae_enc")
            for n, p in self.vae.named_parameters():
                if n in sd["state_dict_vae"]:
                    p.data.copy_(sd["state_dict_vae"][n])

    def _apply_lora_delta(self):
        # back to the pixel + detail weights after the pixel-only pass, without re-adding the deltas every call
        if getattr(self, "_merged_weight", None) is None:
            self._merged_weight = {n: self.lora_deltas_sem[n] + w if n in self.lora_deltas_sem else w
                                   for n, w in self.ori_unet_weight.items()}
        for name, param in self.unet.named_parameters():
            param.data = self._merged_weight[name]

    @property
    def detail_weight(self):
        """omega"""
        return float(self.lambda_sem)

    @detail_weight.setter
    def detail_weight(self, omega):
        assert not self.single_pass or omega == 1.0, "load the model with detail_weight != 1 to change it"
        self.lambda_sem = torch.tensor([float(omega)], device=self.device)

    @property
    def uses_single_pass(self):
        """One U-Net pass when loaded with omega = 1, or when omega is set back to 1."""
        return self.single_pass or float(self.lambda_sem) == 1.0

    @torch.no_grad()
    def empty_prompt_embedding(self):
        return self.encode_prompt([""]).to(dtype=self.weight_dtype)

    def predict_noise(self, z, prompt_embeds):
        """eps for the latent z (whole latent, no tiling)."""
        if not getattr(self, "separate_unets", False):
            return self._predict_no_tiling(z, prompt_embeds, self.uses_single_pass)
        t = torch.ones(1, dtype=torch.long, device=z.device)           # timestep 1
        eps = self.unet(z, t, encoder_hidden_states=prompt_embeds).sample
        if self.uses_single_pass:
            return eps
        eps_pixel = self.unet_pixel(z, t, encoder_hidden_states=prompt_embeds).sample
        eps -= eps_pixel                                                  # same operations as PiSA-SR's two passes
        return self.lambda_pix.to(z.device) * eps_pixel + self.lambda_sem.to(z.device) * eps

    def use_separate_unets(self):
        """Second U-Net with the pixel LoRA only (unet keeps pixel + detail); drops the weight tables of the swap."""
        self.unet_pixel = copy.deepcopy(self.unet)
        for name, p in self.unet_pixel.named_parameters():
            p.data = self.ori_unet_weight[name].clone()
        del self.ori_unet_weight, self.lora_deltas_sem, self._merged_weight
        self.separate_unets = True


def load_one_step_diffusion(checkpoint=paths.STAGE1, sd21=paths.SD21, detail_weight=1.0, precision="fp16",
                            merge_vae_lora=False, separate_unets=False):
    """Stage I in eval mode. detail_weight != 1 selects the two-pass mode, in which it can be changed later; 1 merges
    the detail LoRA into a single pass.
    merge_vae_lora folds the VAE-encoder LoRA into the encoder weights: the encoder runs ~20% faster, but the output is
    no longer bit-identical to the reported results (about 60 dB PSNR apart), so it is off by default."""
    single = detail_weight == 1.0
    args = argparse.Namespace(pretrained_model_path=str(sd21), pretrained_path=str(checkpoint), lambda_pix=1.0,
                              lambda_sem=detail_weight, mixed_precision=precision, default=single,
                              vae_decoder_tiled_size=NO_TILING, vae_encoder_tiled_size=NO_TILING,
                              latent_tiled_size=NO_TILING, latent_tiled_overlap=0, seed=42)
    model = OneStepDiffusion(args)
    model.set_eval()
    model.single_pass = single
    if not single:
        # Use the pixel + detail weights that every two-pass call restores after its pixel-only pass from the start.
        # peft's own merge differs from them in the last bits, which made the first image restored differ slightly
        # (up to 2/255) from later ones.
        model._apply_lora_delta()
        if separate_unets:
            model.use_separate_unets()
    if merge_vae_lora:
        from peft.tuners.lora import LoraLayer
        for mod in model.vae.modules():
            if isinstance(mod, LoraLayer):
                mod.merge()
    return model


def stage1_parameter_count(model, refiner=None):
    """Parameters of everything a configuration runs: text encoder, U-Net with merged LoRAs, VAE, and the refiner
    without the VAE encoder it does not run."""
    n = sum(p.numel() for m in (model.text_encoder, model.unet, model.vae) for p in m.parameters())
    if refiner is not None:
        n += sum(v.numel() for k, v in refiner.state_dict().items() if not k.startswith(("vae.encoder", "vae.quant_conv")))
    return n