Redirect to the authors' Space dipta007/OracleZoom
Browse files- LICENSE-OracleZoom +0 -21
- README.md +7 -47
- app.py +0 -271
- geometry.py +0 -45
- index.html +93 -0
- requirements.txt +0 -28
- samples/0064.png +0 -3
- samples/0245.png +0 -3
- samples/0393.png +0 -3
- samples/0457.png +0 -3
- samples/0479.png +0 -3
- samples/example_16x.png +0 -3
- samples/example_1x.png +0 -3
- samples/example_256x.png +0 -3
- samples/example_256x_input.png +0 -3
- samples/example_4x.png +0 -3
- samples/example_64x.png +0 -3
- samples/example_zoom.mp4 +0 -3
- vendor/COZ-LICENSE +0 -21
- vendor/lora/lora_layers.py +0 -137
- vendor/lora/lora_utils.py +0 -61
- vendor/osediff_sd3.py +0 -913
- vendor/utils/devices.py +0 -138
- vendor/utils/vaehook.py +0 -829
- video.py +0 -57
- zoom.py +0 -138
LICENSE-OracleZoom
DELETED
|
@@ -1,21 +0,0 @@
|
|
| 1 |
-
MIT License
|
| 2 |
-
|
| 3 |
-
Copyright (c) 2026 Shubhashis Roy Dipta, Sourajit Saha, Shaswati Saha, Nobin Sarwar
|
| 4 |
-
|
| 5 |
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
-
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
-
in the Software without restriction, including without limitation the rights
|
| 8 |
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
-
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
-
furnished to do so, subject to the following conditions:
|
| 11 |
-
|
| 12 |
-
The above copyright notice and this permission notice shall be included in all
|
| 13 |
-
copies or substantial portions of the Software.
|
| 14 |
-
|
| 15 |
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
-
SOFTWARE.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -3,64 +3,24 @@ title: OracleZoom Super Resolution
|
|
| 3 |
emoji: 🔎
|
| 4 |
colorFrom: purple
|
| 5 |
colorTo: gray
|
| 6 |
-
sdk:
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
app_file: app.py
|
| 10 |
-
startup_duration_timeout: 45m
|
| 11 |
license: mit
|
| 12 |
-
short_description:
|
| 13 |
tags:
|
| 14 |
- super-resolution
|
| 15 |
- image-to-image
|
| 16 |
-
- diffusion
|
| 17 |
-
- zoom
|
| 18 |
models:
|
| 19 |
- dipta007/OracleZoom
|
| 20 |
-
- stabilityai/stable-diffusion-3-medium-diffusers
|
| 21 |
-
- Qwen/Qwen2.5-VL-3B-Instruct
|
| 22 |
-
datasets:
|
| 23 |
-
- dipta007/OracleZoom-4KLSDB-train
|
| 24 |
---
|
| 25 |
|
| 26 |
-
# OracleZoom 🔎
|
| 27 |
|
| 28 |
-
|
| 29 |
-
*
|
| 30 |
-
pair (current level, next blurry crop) and writes the conditioning for the SD3-medium
|
| 31 |
-
one-step SR transformer that OracleZoom fine-tuned.
|
| 32 |
|
| 33 |
- Paper: https://huggingface.co/papers/2609.06490
|
| 34 |
- Code: https://github.com/dipta007/OracleZoom
|
| 35 |
- Project page: https://dipta007.github.io/OracleZoom/
|
| 36 |
- Model: https://huggingface.co/dipta007/OracleZoom
|
| 37 |
-
- Training data: https://huggingface.co/datasets/dipta007/OracleZoom-4KLSDB-train
|
| 38 |
-
- Authors' own Space: https://huggingface.co/spaces/dipta007/OracleZoom
|
| 39 |
-
|
| 40 |
-
## What is in here
|
| 41 |
-
|
| 42 |
-
`app.py` is the interface. The pipeline itself is the authors' inference path, reused
|
| 43 |
-
unchanged from `space/` in [dipta007/OracleZoom](https://github.com/dipta007/OracleZoom)
|
| 44 |
-
(MIT, see `LICENSE-OracleZoom`): `zoom.py` is the recursion, `geometry.py` the crop
|
| 45 |
-
geometry, `video.py` the clip renderer, and `vendor/` holds the four files copied from
|
| 46 |
-
[Chain-of-Zoom](https://github.com/bryanswkim/Chain-of-Zoom) (MIT, see `vendor/COZ-LICENSE`).
|
| 47 |
-
|
| 48 |
-
The example images in `samples/` are the authors' own, shipped in the MIT-licensed repo and
|
| 49 |
-
drawn from their [4KLSDB set](https://huggingface.co/datasets/dipta007/OracleZoom-4KLSDB-train)
|
| 50 |
-
(CC-BY-4.0) — credit to Shubhashis Roy Dipta, Sourajit Saha, Shaswati Saha and Nobin Sarwar.
|
| 51 |
-
|
| 52 |
-
## Notes for anyone duplicating this
|
| 53 |
-
|
| 54 |
-
- Needs an `HF_TOKEN` secret: Stable Diffusion 3-medium is gated, so the token must come from
|
| 55 |
-
an account that accepted its licence. `huggingface_hub` picks the variable up on its own.
|
| 56 |
-
- Hardware: ZeroGPU. The pipeline holds roughly 27 GB of weights, inside the 48 GB a `large`
|
| 57 |
-
slice gives. Boot streams SD3-medium, Qwen2.5-VL-3B and the merged transformer, so the
|
| 58 |
-
startup timeout is raised to 45 minutes.
|
| 59 |
-
- Gradio is pinned to the 5.x series on purpose: `transformers==4.49.0` (what this pipeline
|
| 60 |
-
was built against) requires `huggingface-hub<1.0`, which gradio 6 cannot satisfy.
|
| 61 |
-
|
| 62 |
-
## Honest limits
|
| 63 |
-
|
| 64 |
-
Past the first step or two there is no ground truth left to recover, so the deep levels are
|
| 65 |
-
plausible detail rather than measured detail. The zoom is a crop of your photo, not a real
|
| 66 |
-
lens moving closer. The paper is explicit about which claims it does and does not make.
|
|
|
|
| 3 |
emoji: 🔎
|
| 4 |
colorFrom: purple
|
| 5 |
colorTo: gray
|
| 6 |
+
sdk: static
|
| 7 |
+
app_file: index.html
|
| 8 |
+
pinned: false
|
|
|
|
|
|
|
| 9 |
license: mit
|
| 10 |
+
short_description: Moved — use the authors' Space dipta007/OracleZoom
|
| 11 |
tags:
|
| 12 |
- super-resolution
|
| 13 |
- image-to-image
|
|
|
|
|
|
|
| 14 |
models:
|
| 15 |
- dipta007/OracleZoom
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
---
|
| 17 |
|
| 18 |
+
# OracleZoom has moved 🔎
|
| 19 |
|
| 20 |
+
This Space is now a redirect to the authors' own demo:
|
| 21 |
+
**https://huggingface.co/spaces/dipta007/OracleZoom**
|
|
|
|
|
|
|
| 22 |
|
| 23 |
- Paper: https://huggingface.co/papers/2609.06490
|
| 24 |
- Code: https://github.com/dipta007/OracleZoom
|
| 25 |
- Project page: https://dipta007.github.io/OracleZoom/
|
| 26 |
- Model: https://huggingface.co/dipta007/OracleZoom
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
DELETED
|
@@ -1,271 +0,0 @@
|
|
| 1 |
-
"""OracleZoom — reference-constrained recursive image super-resolution.
|
| 2 |
-
|
| 3 |
-
Upload a photo, pick a point, and zoom into it 4x at a time. Each step super-resolves the
|
| 4 |
-
previous step's own output, so four steps reach 256x. A Qwen2.5-VL prompter reads the pair
|
| 5 |
-
(current level, next blurry crop) and writes the conditioning for the SD3 one-step SR model.
|
| 6 |
-
|
| 7 |
-
The pipeline is the authors' own inference path, taken from
|
| 8 |
-
https://github.com/dipta007/OracleZoom (MIT): `zoom.py`, `geometry.py`, `video.py` and
|
| 9 |
-
`vendor/` are theirs; only the interface below is new.
|
| 10 |
-
"""
|
| 11 |
-
import os
|
| 12 |
-
import time
|
| 13 |
-
|
| 14 |
-
# High-res pixel-space VAE decodes spike the allocator on ZeroGPU. Must precede any torch import.
|
| 15 |
-
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 16 |
-
# The VLM prompter tokenizes in the main process and every request forks a GPU worker.
|
| 17 |
-
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 18 |
-
|
| 19 |
-
import spaces # noqa: E402 — before anything that touches torch/CUDA
|
| 20 |
-
import gradio as gr # noqa: E402
|
| 21 |
-
from PIL import Image, ImageDraw # noqa: E402
|
| 22 |
-
|
| 23 |
-
import geometry # noqa: E402
|
| 24 |
-
import video # noqa: E402
|
| 25 |
-
import zoom # noqa: E402
|
| 26 |
-
|
| 27 |
-
UPSCALE = 4
|
| 28 |
-
DEFAULT_STEPS = 4
|
| 29 |
-
MAX_STEPS = 8
|
| 30 |
-
ACCENT = "#a855f7"
|
| 31 |
-
SAMPLES = ["0479", "0064", "0245", "0393", "0457"]
|
| 32 |
-
|
| 33 |
-
# A real run, shipped with the Space, so the page shows the payoff before anyone spends quota.
|
| 34 |
-
EXAMPLE_INPUT = "samples/example_1x.png"
|
| 35 |
-
EXAMPLE_CLIP = "samples/example_zoom.mp4"
|
| 36 |
-
EXAMPLE_LEVELS = [(EXAMPLE_INPUT, "input")] + \
|
| 37 |
-
[(f"samples/example_{f}x.png", f"{f}x") for f in (4, 16, 64, 256)]
|
| 38 |
-
EXAMPLE_COMPARE = ("samples/example_256x_input.png", "samples/example_256x.png")
|
| 39 |
-
EXAMPLE_NOTE = "_From the shipped example run. Zoom your own photo to see its prompts._"
|
| 40 |
-
|
| 41 |
-
MODELS = zoom.Models()
|
| 42 |
-
|
| 43 |
-
HEADER = """# OracleZoom 🔎
|
| 44 |
-
|
| 45 |
-
Zoom into a photo far past what it holds. Every step is a 4x super-resolution of the
|
| 46 |
-
*previous step's own output*, so four steps reach **256x** — and the last available
|
| 47 |
-
reference keeps each step honest about the structure it can still verify.
|
| 48 |
-
|
| 49 |
-
[Paper](https://huggingface.co/papers/2609.06490) ·
|
| 50 |
-
[Code](https://github.com/dipta007/OracleZoom) ·
|
| 51 |
-
[Project page](https://dipta007.github.io/OracleZoom/) ·
|
| 52 |
-
[Model](https://huggingface.co/dipta007/OracleZoom) ·
|
| 53 |
-
[Training data](https://huggingface.co/datasets/dipta007/OracleZoom-4KLSDB-train)
|
| 54 |
-
"""
|
| 55 |
-
|
| 56 |
-
BIBTEX = """@misc{dipta2026oraclezoomonpolicyselfdistillationinspired,
|
| 57 |
-
title = {OracleZoom: On-Policy Self-Distillation Inspired Reference-Constrained Recursive Image Super Resolution},
|
| 58 |
-
author = {Shubhashis Roy Dipta and Sourajit Saha and Shaswati Saha and Nobin Sarwar},
|
| 59 |
-
year = {2026},
|
| 60 |
-
eprint = {2609.06490},
|
| 61 |
-
archivePrefix = {arXiv},
|
| 62 |
-
primaryClass = {cs.CV},
|
| 63 |
-
url = {https://arxiv.org/abs/2609.06490}
|
| 64 |
-
}"""
|
| 65 |
-
|
| 66 |
-
CSS = """
|
| 67 |
-
#col-container {max-width: 1400px; margin: 0 auto;}
|
| 68 |
-
.dark .gradio-container {color: var(--body-text-color);}
|
| 69 |
-
#zoom-target img {cursor: crosshair;}
|
| 70 |
-
#clip video {border-radius: 12px;}
|
| 71 |
-
/* The level strip should hug its content: .gallery-container carries the height, so styling
|
| 72 |
-
only the outer block leaves a dead band below the thumbnails. */
|
| 73 |
-
#levels, #levels .gallery-container, #levels .grid-wrap {
|
| 74 |
-
height: auto !important; max-height: none !important; min-height: 0 !important;
|
| 75 |
-
overflow: visible !important; flex-grow: 0 !important;}
|
| 76 |
-
"""
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
def label(factor: int) -> str:
|
| 80 |
-
return "input" if factor == 1 else f"{factor}x"
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
def scale_labels(levels: int) -> list:
|
| 84 |
-
return [label(UPSCALE ** n) for n in range(1, int(levels) + 1)]
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
def preview(image, levels, cx, cy):
|
| 88 |
-
"""Draw the nested zoom windows so nobody spends a run to find out where it lands.
|
| 89 |
-
|
| 90 |
-
Falls back to the shipped example when nothing is uploaded yet — returning None would
|
| 91 |
-
blank the box the moment a visitor touched a slider.
|
| 92 |
-
"""
|
| 93 |
-
canvas = geometry.resize_and_center_crop(
|
| 94 |
-
image if image is not None else Image.open(EXAMPLE_INPUT)).convert("RGB")
|
| 95 |
-
rects = geometry.nested_rects(canvas.size, int(levels), UPSCALE, (cx, cy))
|
| 96 |
-
dimmed = Image.blend(canvas, Image.new("RGB", canvas.size, (0, 0, 0)), 0.45)
|
| 97 |
-
if rects:
|
| 98 |
-
dimmed.paste(canvas.crop(rects[0]), rects[0][:2])
|
| 99 |
-
draw = ImageDraw.Draw(dimmed)
|
| 100 |
-
for rect in rects:
|
| 101 |
-
if rect[2] - rect[0] >= 3:
|
| 102 |
-
draw.rectangle(rect, outline=ACCENT, width=2)
|
| 103 |
-
return dimmed
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
def pick_point(evt: gr.SelectData):
|
| 107 |
-
"""Move the zoom point to wherever the preview was clicked (rounded to the slider step)."""
|
| 108 |
-
x, y = evt.index
|
| 109 |
-
return (round(x / geometry.PROCESS_SIZE, 2), round(y / geometry.PROCESS_SIZE, 2))
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
def gallery_shape(levels):
|
| 113 |
-
"""One row up to five levels, two beyond. Height is left to CSS, which hugs the content."""
|
| 114 |
-
items = int(levels) + 1
|
| 115 |
-
rows = 1 if items <= 5 else 2
|
| 116 |
-
return gr.update(columns=-(-items // rows), rows=rows)
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
def _gallery_items(gallery):
|
| 120 |
-
"""Gradio hands a filepath-typed Gallery back to a handler as (path, caption) tuples."""
|
| 121 |
-
return [(item[0], item[1] or "") for item in gallery or []
|
| 122 |
-
if isinstance(item, (tuple, list)) and len(item) == 2]
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
def compare_level(gallery, cx, cy, evt: gr.SelectData):
|
| 126 |
-
"""Rebuild the before/after for whichever level was clicked in the strip.
|
| 127 |
-
|
| 128 |
-
The left side is the plain bicubic enlargement that fed that step; recomputing it from the
|
| 129 |
-
levels already on screen keeps this on the CPU, so browsing costs no GPU quota.
|
| 130 |
-
"""
|
| 131 |
-
items = _gallery_items(gallery)
|
| 132 |
-
index = getattr(evt, "index", None)
|
| 133 |
-
i = index if isinstance(index, int) else None
|
| 134 |
-
if i is None or i <= 0 or i >= len(items):
|
| 135 |
-
return gr.skip()
|
| 136 |
-
prev = Image.open(items[i - 1][0]).convert("RGB")
|
| 137 |
-
blurry = geometry.zoom_window(prev, UPSCALE, (cx, cy)).resize(prev.size, Image.BICUBIC)
|
| 138 |
-
return blurry, Image.open(items[i][0]).convert("RGB")
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
def estimate_duration(image=None, levels=DEFAULT_STEPS, *args, **kwargs):
|
| 142 |
-
"""GPU seconds to reserve. Checked against the visitor's remaining quota before the run,
|
| 143 |
-
so a loose number locks out low-quota visitors and a tight one gets the run killed.
|
| 144 |
-
|
| 145 |
-
Measured on this Space (warm worker, 512x512): 3.8 s at 1 step, 7.9 s at 4, 15.6 s at 8
|
| 146 |
-
— about 2 s fixed plus 1.8 s per step, clip rendering included. Doubled, which is what
|
| 147 |
-
covers a cold worker streaming the ~27 GB of weights into VRAM first.
|
| 148 |
-
"""
|
| 149 |
-
return int(6 + 3 * int(levels or DEFAULT_STEPS))
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
def _zoom_stream(image, levels, cx, cy):
|
| 153 |
-
if image is None:
|
| 154 |
-
raise gr.Error("Upload a photo first (or click one of the examples).")
|
| 155 |
-
levels = int(levels)
|
| 156 |
-
started = time.perf_counter()
|
| 157 |
-
gallery, prompts, frames, compare = [], [], [], None
|
| 158 |
-
for step, factor, prompt, blurry, result in zoom.zoom(MODELS, image, levels, UPSCALE,
|
| 159 |
-
(cx, cy)):
|
| 160 |
-
frames.append(result)
|
| 161 |
-
gallery.append((result, label(factor)))
|
| 162 |
-
if step:
|
| 163 |
-
compare = (blurry, result)
|
| 164 |
-
prompts.append(f"**{label(factor)}** {prompt or '_(no prompt)_'}")
|
| 165 |
-
left = levels - step
|
| 166 |
-
if not step:
|
| 167 |
-
status = f"Warmed up. Zooming {levels} step{'s' if levels > 1 else ''}…"
|
| 168 |
-
elif left:
|
| 169 |
-
status = f"At **{label(factor)}**, {left} step{'s' if left > 1 else ''} to go…"
|
| 170 |
-
else:
|
| 171 |
-
status = f"At **{label(factor)}**. Rendering the clip…"
|
| 172 |
-
# The first yield clears any clip left from the previous run; later ones leave it alone.
|
| 173 |
-
yield (status, (None if not step else gr.skip()), gallery, compare,
|
| 174 |
-
"\n\n".join(prompts))
|
| 175 |
-
clip = video.render(frames, UPSCALE, (cx, cy))
|
| 176 |
-
elapsed = time.perf_counter() - started
|
| 177 |
-
print(f"#### zoom: {levels} step(s) in {elapsed:.1f}s", flush=True)
|
| 178 |
-
yield (f"Done. Reached **{label(UPSCALE ** levels)}** in {elapsed:.0f}s. "
|
| 179 |
-
f"Click any level below to compare it with the plain enlargement it started from.",
|
| 180 |
-
clip, gallery, compare, "\n\n".join(prompts))
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
@spaces.GPU(duration=estimate_duration)
|
| 184 |
-
def zoom_in(image, levels: int = DEFAULT_STEPS, cx: float = 0.5, cy: float = 0.5):
|
| 185 |
-
"""Recursively super-resolve an image, 4x per step, streaming each level as it lands.
|
| 186 |
-
|
| 187 |
-
Args:
|
| 188 |
-
image: the photo to zoom into (resized and centre-cropped to 512x512 first).
|
| 189 |
-
levels: how many 4x steps to take. 4 steps reach 256x.
|
| 190 |
-
cx: horizontal position of the zoom point, 0 (left) to 1 (right).
|
| 191 |
-
cy: vertical position of the zoom point, 0 (top) to 1 (bottom).
|
| 192 |
-
"""
|
| 193 |
-
yield from _zoom_stream(image, levels, cx, cy)
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
@spaces.GPU(duration=estimate_duration)
|
| 197 |
-
def zoom_in_cached(image, levels: int = DEFAULT_STEPS, cx: float = 0.5, cy: float = 0.5):
|
| 198 |
-
"""Same zoom, returned in one shot so `gr.Examples` can cache it.
|
| 199 |
-
|
| 200 |
-
Args:
|
| 201 |
-
image: the photo to zoom into.
|
| 202 |
-
levels: how many 4x steps to take.
|
| 203 |
-
cx: horizontal position of the zoom point, 0 to 1.
|
| 204 |
-
cy: vertical position of the zoom point, 0 to 1.
|
| 205 |
-
"""
|
| 206 |
-
final = None
|
| 207 |
-
for final in _zoom_stream(image, levels, cx, cy):
|
| 208 |
-
pass
|
| 209 |
-
return final
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
with gr.Blocks(theme=gr.themes.Citrus(), css=CSS, title="OracleZoom: zoom past 256x") as demo:
|
| 213 |
-
with gr.Column(elem_id="col-container"):
|
| 214 |
-
gr.Markdown(HEADER)
|
| 215 |
-
with gr.Row(equal_height=False):
|
| 216 |
-
with gr.Column(scale=4):
|
| 217 |
-
image = gr.Image(label="Your photo", type="pil", height=260,
|
| 218 |
-
sources=["upload", "clipboard"])
|
| 219 |
-
# Seeded with the shipped example so the overlay explains itself before any upload.
|
| 220 |
-
target = gr.Image(value=preview(None, DEFAULT_STEPS, 0.5, 0.5),
|
| 221 |
-
elem_id="zoom-target", label="Click to move the zoom point",
|
| 222 |
-
type="pil", interactive=False, height=330,
|
| 223 |
-
show_download_button=False)
|
| 224 |
-
levels = gr.Slider(1, MAX_STEPS, value=DEFAULT_STEPS, step=1, label="Zoom steps",
|
| 225 |
-
info=f"Each step is {UPSCALE}x. 4 steps reach 256x, "
|
| 226 |
-
f"{MAX_STEPS} reach {UPSCALE ** MAX_STEPS}x. "
|
| 227 |
-
f"Fewer steps finish sooner.")
|
| 228 |
-
go = gr.Button("🔎 Zoom in", variant="primary", size="lg")
|
| 229 |
-
status = gr.Markdown("Showing a shipped example run. "
|
| 230 |
-
"Upload a photo, then press **Zoom in**.")
|
| 231 |
-
with gr.Accordion("Advanced settings", open=False):
|
| 232 |
-
cx = gr.Slider(0, 1, value=0.5, step=0.01, label="Zoom point — horizontal")
|
| 233 |
-
cy = gr.Slider(0, 1, value=0.5, step=0.01, label="Zoom point — vertical")
|
| 234 |
-
gr.Markdown("##### What the VLM prompter read at each step")
|
| 235 |
-
prompts = gr.Markdown(EXAMPLE_NOTE)
|
| 236 |
-
with gr.Column(scale=6):
|
| 237 |
-
clip = gr.Video(value=EXAMPLE_CLIP, label="The zoom", elem_id="clip",
|
| 238 |
-
autoplay=True, loop=True, height=460)
|
| 239 |
-
gallery = gr.Gallery(value=EXAMPLE_LEVELS, label="Every level, start to finish",
|
| 240 |
-
elem_id="levels", columns=5, rows=1, object_fit="cover",
|
| 241 |
-
allow_preview=False, show_download_button=True)
|
| 242 |
-
compare = gr.ImageSlider(value=EXAMPLE_COMPARE, height=470, elem_id="compare",
|
| 243 |
-
label="Plain enlargement (left) vs OracleZoom (right)")
|
| 244 |
-
|
| 245 |
-
gr.Examples(
|
| 246 |
-
examples=[f"samples/{n}.png" for n in SAMPLES],
|
| 247 |
-
inputs=[image],
|
| 248 |
-
outputs=[status, clip, gallery, compare, prompts],
|
| 249 |
-
fn=zoom_in_cached,
|
| 250 |
-
cache_examples=True,
|
| 251 |
-
cache_mode="lazy",
|
| 252 |
-
label="The authors' own example photos — click one to zoom it to 256x",
|
| 253 |
-
)
|
| 254 |
-
|
| 255 |
-
gr.Markdown("**Honest limits.** Past the first step or two there is no ground truth "
|
| 256 |
-
"left to recover, so deep levels are plausible detail, not measured "
|
| 257 |
-
"detail. The zoom is a crop of your photo, not a lens moving closer.")
|
| 258 |
-
gr.Markdown("### Cite")
|
| 259 |
-
gr.Code(value=BIBTEX, language=None, show_label=False, container=False)
|
| 260 |
-
|
| 261 |
-
controls = [image, levels, cx, cy]
|
| 262 |
-
for control in controls:
|
| 263 |
-
control.change(preview, controls, target, show_api=False)
|
| 264 |
-
target.select(pick_point, None, [cx, cy], show_api=False)
|
| 265 |
-
# Reshape before the run, not after, so the strip does not resize under the results.
|
| 266 |
-
levels.change(gallery_shape, levels, gallery, show_api=False)
|
| 267 |
-
go.click(zoom_in, controls, [status, clip, gallery, compare, prompts], api_name="zoom_in")
|
| 268 |
-
gallery.select(compare_level, [gallery, cx, cy], compare, show_api=False)
|
| 269 |
-
|
| 270 |
-
if __name__ == "__main__":
|
| 271 |
-
demo.queue(max_size=20).launch(mcp_server=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
geometry.py
DELETED
|
@@ -1,45 +0,0 @@
|
|
| 1 |
-
"""Crop geometry for the zoom. Pure PIL, no model code, so it stays cheap to test."""
|
| 2 |
-
from PIL import Image
|
| 3 |
-
|
| 4 |
-
PROCESS_SIZE = 512
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
def resize_and_center_crop(img, size=PROCESS_SIZE):
|
| 8 |
-
"""CoZ's entry crop: shortest side to `size`, then the centre square."""
|
| 9 |
-
w, h = img.size
|
| 10 |
-
scale = size / min(w, h)
|
| 11 |
-
nw, nh = int(w * scale), int(h * scale)
|
| 12 |
-
img = img.resize((nw, nh), Image.LANCZOS)
|
| 13 |
-
left, top = (nw - size) // 2, (nh - size) // 2
|
| 14 |
-
return img.crop((left, top, left + size, top + size))
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
def window_rect(size, upscale, center=(0.5, 0.5)):
|
| 18 |
-
"""The next level's field of view: a 1/upscale square around `center`, clamped inside."""
|
| 19 |
-
w, h = size
|
| 20 |
-
nw, nh = w // upscale, h // upscale
|
| 21 |
-
left = min(max(int(center[0] * w - nw / 2), 0), w - nw)
|
| 22 |
-
top = min(max(int(center[1] * h - nh / 2), 0), h - nh)
|
| 23 |
-
return left, top, left + nw, top + nh
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
def zoom_window(img, upscale, center=(0.5, 0.5)):
|
| 27 |
-
return img.crop(window_rect(img.size, upscale, center))
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
def nested_rects(size, levels, upscale=4, center=(0.5, 0.5)):
|
| 31 |
-
"""Where each zoom step lands, in the coordinates of the first canvas.
|
| 32 |
-
|
| 33 |
-
Stops early once a window would be under a pixel wide. The recursion itself never
|
| 34 |
-
degenerates, because every step resizes its crop back up to the full canvas, but drawing
|
| 35 |
-
all those steps onto the one original canvas does run out of pixels.
|
| 36 |
-
"""
|
| 37 |
-
rects, rect = [], (0, 0, size[0], size[1])
|
| 38 |
-
for _ in range(levels):
|
| 39 |
-
side = rect[2] - rect[0]
|
| 40 |
-
if side // upscale < 1:
|
| 41 |
-
break
|
| 42 |
-
l, t, r, b = window_rect((side, side), upscale, center)
|
| 43 |
-
rect = (rect[0] + l, rect[1] + t, rect[0] + r, rect[1] + b)
|
| 44 |
-
rects.append(rect)
|
| 45 |
-
return rects
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index.html
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
+
<title>OracleZoom has moved</title>
|
| 7 |
+
<link rel="canonical" href="https://huggingface.co/spaces/dipta007/OracleZoom">
|
| 8 |
+
<style>
|
| 9 |
+
:root {
|
| 10 |
+
color-scheme: light dark;
|
| 11 |
+
--bg: #ffffff;
|
| 12 |
+
--fg: #111827;
|
| 13 |
+
--muted: #6b7280;
|
| 14 |
+
--line: #e5e7eb;
|
| 15 |
+
--accent: #ff9d00;
|
| 16 |
+
}
|
| 17 |
+
@media (prefers-color-scheme: dark) {
|
| 18 |
+
:root { --bg: #0b0f19; --fg: #f3f4f6; --muted: #9ca3af; --line: #1f2937; }
|
| 19 |
+
}
|
| 20 |
+
* { box-sizing: border-box; }
|
| 21 |
+
body {
|
| 22 |
+
margin: 0;
|
| 23 |
+
min-height: 100vh;
|
| 24 |
+
display: flex;
|
| 25 |
+
align-items: center;
|
| 26 |
+
justify-content: center;
|
| 27 |
+
padding: 24px;
|
| 28 |
+
background: var(--bg);
|
| 29 |
+
color: var(--fg);
|
| 30 |
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
| 31 |
+
line-height: 1.55;
|
| 32 |
+
}
|
| 33 |
+
main { max-width: 34rem; text-align: center; }
|
| 34 |
+
.emoji { font-size: 3rem; line-height: 1; }
|
| 35 |
+
h1 { font-size: 1.5rem; font-weight: 650; margin: 1rem 0 0.5rem; }
|
| 36 |
+
p { color: var(--muted); margin: 0 0 1.5rem; }
|
| 37 |
+
a.cta {
|
| 38 |
+
display: inline-block;
|
| 39 |
+
padding: 0.75rem 1.5rem;
|
| 40 |
+
border-radius: 0.65rem;
|
| 41 |
+
background: var(--accent);
|
| 42 |
+
color: #111827;
|
| 43 |
+
font-weight: 600;
|
| 44 |
+
text-decoration: none;
|
| 45 |
+
}
|
| 46 |
+
a.cta:hover { filter: brightness(1.05); }
|
| 47 |
+
.links {
|
| 48 |
+
margin-top: 2rem;
|
| 49 |
+
padding-top: 1.25rem;
|
| 50 |
+
border-top: 1px solid var(--line);
|
| 51 |
+
font-size: 0.875rem;
|
| 52 |
+
color: var(--muted);
|
| 53 |
+
}
|
| 54 |
+
.links a { color: inherit; }
|
| 55 |
+
.count { font-variant-numeric: tabular-nums; }
|
| 56 |
+
</style>
|
| 57 |
+
</head>
|
| 58 |
+
<body>
|
| 59 |
+
<main>
|
| 60 |
+
<div class="emoji">🔎</div>
|
| 61 |
+
<h1>OracleZoom lives on the authors' Space</h1>
|
| 62 |
+
<p>
|
| 63 |
+
Redirecting to <strong>dipta007/OracleZoom</strong> in <span class="count" id="count">3</span>s.
|
| 64 |
+
</p>
|
| 65 |
+
<a class="cta" id="go" href="https://huggingface.co/spaces/dipta007/OracleZoom" target="_top" rel="noopener">
|
| 66 |
+
Open OracleZoom →
|
| 67 |
+
</a>
|
| 68 |
+
<div class="links">
|
| 69 |
+
<a href="https://huggingface.co/papers/2609.06490" target="_blank" rel="noopener">Paper</a> ·
|
| 70 |
+
<a href="https://github.com/dipta007/OracleZoom" target="_blank" rel="noopener">Code</a> ·
|
| 71 |
+
<a href="https://dipta007.github.io/OracleZoom/" target="_blank" rel="noopener">Project page</a> ·
|
| 72 |
+
<a href="https://huggingface.co/dipta007/OracleZoom" target="_blank" rel="noopener">Model</a>
|
| 73 |
+
</div>
|
| 74 |
+
</main>
|
| 75 |
+
<script>
|
| 76 |
+
const TARGET = "https://huggingface.co/spaces/dipta007/OracleZoom";
|
| 77 |
+
const el = document.getElementById("count");
|
| 78 |
+
let n = 3;
|
| 79 |
+
const tick = setInterval(() => {
|
| 80 |
+
n -= 1;
|
| 81 |
+
if (n > 0) { el.textContent = n; return; }
|
| 82 |
+
clearInterval(tick);
|
| 83 |
+
el.textContent = 0;
|
| 84 |
+
try {
|
| 85 |
+
if (window.top !== window.self) { window.top.location.replace(TARGET); }
|
| 86 |
+
else { window.location.replace(TARGET); }
|
| 87 |
+
} catch (e) {
|
| 88 |
+
document.getElementById("go").focus();
|
| 89 |
+
}
|
| 90 |
+
}, 1000);
|
| 91 |
+
</script>
|
| 92 |
+
</body>
|
| 93 |
+
</html>
|
requirements.txt
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
# Pins taken from the authors' own Space (github.com/dipta007/OracleZoom, space/requirements.txt).
|
| 2 |
-
# diffusers stays at the paper's version: the SR LoRA is injected by matching the class name
|
| 3 |
-
# "AdaLayerNormZero", so a diffusers rename would silently inject nothing.
|
| 4 |
-
# transformers 4.49.0 is what the Qwen2.5-VL prompter + peft merge path was built against; it
|
| 5 |
-
# also pins huggingface-hub<1.0, which is why the Space runs gradio 5.50 rather than 6.x.
|
| 6 |
-
# gradio / spaces / huggingface_hub are deliberately absent (platform-managed).
|
| 7 |
-
torch==2.8.0
|
| 8 |
-
torchvision==0.23.0
|
| 9 |
-
diffusers==0.32.2
|
| 10 |
-
transformers==4.49.0
|
| 11 |
-
accelerate
|
| 12 |
-
peft>=0.13
|
| 13 |
-
safetensors
|
| 14 |
-
qwen-vl-utils
|
| 15 |
-
# SD3's third text encoder is T5. Its fast tokenizer is built from a sentencepiece model, and
|
| 16 |
-
# the conversion wants protobuf. Both arrive transitively in the research env, not here.
|
| 17 |
-
sentencepiece
|
| 18 |
-
protobuf
|
| 19 |
-
lpips
|
| 20 |
-
einops
|
| 21 |
-
pyyaml
|
| 22 |
-
tqdm
|
| 23 |
-
numpy
|
| 24 |
-
pillow
|
| 25 |
-
imageio
|
| 26 |
-
imageio-ffmpeg
|
| 27 |
-
# demo.launch(mcp_server=True)
|
| 28 |
-
mcp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
samples/0064.png
DELETED
Git LFS Details
|
samples/0245.png
DELETED
Git LFS Details
|
samples/0393.png
DELETED
Git LFS Details
|
samples/0457.png
DELETED
Git LFS Details
|
samples/0479.png
DELETED
Git LFS Details
|
samples/example_16x.png
DELETED
Git LFS Details
|
samples/example_1x.png
DELETED
Git LFS Details
|
samples/example_256x.png
DELETED
Git LFS Details
|
samples/example_256x_input.png
DELETED
Git LFS Details
|
samples/example_4x.png
DELETED
Git LFS Details
|
samples/example_64x.png
DELETED
Git LFS Details
|
samples/example_zoom.mp4
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:dc9b6172522eb830313cf1f18f9bb852706886005850dffd80e45a66f91391c7
|
| 3 |
-
size 3691325
|
|
|
|
|
|
|
|
|
|
|
|
vendor/COZ-LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
| 1 |
-
MIT License
|
| 2 |
-
|
| 3 |
-
Copyright (c) 2025 Bryan Sangwoo Kim
|
| 4 |
-
|
| 5 |
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
-
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
-
in the Software without restriction, including without limitation the rights
|
| 8 |
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
-
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
-
furnished to do so, subject to the following conditions:
|
| 11 |
-
|
| 12 |
-
The above copyright notice and this permission notice shall be included in all
|
| 13 |
-
copies or substantial portions of the Software.
|
| 14 |
-
|
| 15 |
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
-
SOFTWARE.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vendor/lora/lora_layers.py
DELETED
|
@@ -1,137 +0,0 @@
|
|
| 1 |
-
from typing import List, Optional, Set, Type, Union
|
| 2 |
-
|
| 3 |
-
import torch
|
| 4 |
-
from torch import nn
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
class LoraInjectedLinear(nn.Module):
|
| 8 |
-
"""
|
| 9 |
-
Linear layer with LoRA injection.
|
| 10 |
-
Taken from https://github.com/cloneofsimo/lora/blob/master/lora_diffusion/lora.py
|
| 11 |
-
"""
|
| 12 |
-
def __init__(
|
| 13 |
-
self, in_features, out_features, bias=False, r=4, dropout_p=0.1, scale=1.0
|
| 14 |
-
):
|
| 15 |
-
super().__init__()
|
| 16 |
-
|
| 17 |
-
if r > min(in_features, out_features):
|
| 18 |
-
raise ValueError(
|
| 19 |
-
f"LoRA rank {r} must be less or equal than {min(in_features, out_features)}"
|
| 20 |
-
)
|
| 21 |
-
self.r = r
|
| 22 |
-
self.linear = nn.Linear(in_features, out_features, bias)
|
| 23 |
-
self.lora_down = nn.Linear(in_features, r, bias=False)
|
| 24 |
-
self.dropout = nn.Dropout(dropout_p)
|
| 25 |
-
self.lora_up = nn.Linear(r, out_features, bias=False)
|
| 26 |
-
self.scale = scale
|
| 27 |
-
self.selector = nn.Identity()
|
| 28 |
-
|
| 29 |
-
nn.init.normal_(self.lora_down.weight, std=1 / r)
|
| 30 |
-
nn.init.zeros_(self.lora_up.weight)
|
| 31 |
-
|
| 32 |
-
def forward(self, input):
|
| 33 |
-
return (
|
| 34 |
-
self.linear(input.float())
|
| 35 |
-
+ self.dropout(self.lora_up(self.selector(self.lora_down(input.float()))))
|
| 36 |
-
* self.scale
|
| 37 |
-
).half()
|
| 38 |
-
|
| 39 |
-
def realize_as_lora(self):
|
| 40 |
-
return self.lora_up.weight.data * self.scale, self.lora_down.weight.data
|
| 41 |
-
|
| 42 |
-
def set_selector_from_diag(self, diag: torch.Tensor):
|
| 43 |
-
# diag is a 1D tensor of size (r,)
|
| 44 |
-
assert diag.shape == (self.r,)
|
| 45 |
-
self.selector = nn.Linear(self.r, self.r, bias=False)
|
| 46 |
-
self.selector.weight.data = torch.diag(diag)
|
| 47 |
-
self.selector.weight.data = self.selector.weight.data.to(
|
| 48 |
-
self.lora_up.weight.device
|
| 49 |
-
).to(self.lora_up.weight.dtype)
|
| 50 |
-
|
| 51 |
-
class LoraInjectedConv2d(nn.Module):
|
| 52 |
-
def __init__(
|
| 53 |
-
self,
|
| 54 |
-
in_channels: int,
|
| 55 |
-
out_channels: int,
|
| 56 |
-
kernel_size,
|
| 57 |
-
stride=1,
|
| 58 |
-
padding=0,
|
| 59 |
-
dilation=1,
|
| 60 |
-
groups: int = 1,
|
| 61 |
-
bias: bool = True,
|
| 62 |
-
r: int = 4,
|
| 63 |
-
dropout_p: float = 0.1,
|
| 64 |
-
scale: float = 1.0,
|
| 65 |
-
):
|
| 66 |
-
super().__init__()
|
| 67 |
-
if r > min(in_channels, out_channels):
|
| 68 |
-
raise ValueError(
|
| 69 |
-
f"LoRA rank {r} must be less or equal than {min(in_channels, out_channels)}"
|
| 70 |
-
)
|
| 71 |
-
self.r = r
|
| 72 |
-
self.conv = nn.Conv2d(
|
| 73 |
-
in_channels=in_channels,
|
| 74 |
-
out_channels=out_channels,
|
| 75 |
-
kernel_size=kernel_size,
|
| 76 |
-
stride=stride,
|
| 77 |
-
padding=padding,
|
| 78 |
-
dilation=dilation,
|
| 79 |
-
groups=groups,
|
| 80 |
-
bias=bias,
|
| 81 |
-
)
|
| 82 |
-
|
| 83 |
-
self.lora_down = nn.Conv2d(
|
| 84 |
-
in_channels=in_channels,
|
| 85 |
-
out_channels=r,
|
| 86 |
-
kernel_size=kernel_size,
|
| 87 |
-
stride=stride,
|
| 88 |
-
padding=padding,
|
| 89 |
-
dilation=dilation,
|
| 90 |
-
groups=groups,
|
| 91 |
-
bias=False,
|
| 92 |
-
)
|
| 93 |
-
self.dropout = nn.Dropout(dropout_p)
|
| 94 |
-
self.lora_up = nn.Conv2d(
|
| 95 |
-
in_channels=r,
|
| 96 |
-
out_channels=out_channels,
|
| 97 |
-
kernel_size=1,
|
| 98 |
-
stride=1,
|
| 99 |
-
padding=0,
|
| 100 |
-
bias=False,
|
| 101 |
-
)
|
| 102 |
-
self.selector = nn.Identity()
|
| 103 |
-
self.scale = scale
|
| 104 |
-
|
| 105 |
-
nn.init.normal_(self.lora_down.weight, std=1 / r)
|
| 106 |
-
nn.init.zeros_(self.lora_up.weight)
|
| 107 |
-
|
| 108 |
-
def forward(self, input):
|
| 109 |
-
return (
|
| 110 |
-
self.conv(input)
|
| 111 |
-
+ self.dropout(self.lora_up(self.selector(self.lora_down(input))))
|
| 112 |
-
* self.scale
|
| 113 |
-
)
|
| 114 |
-
|
| 115 |
-
def realize_as_lora(self):
|
| 116 |
-
return self.lora_up.weight.data * self.scale, self.lora_down.weight.data
|
| 117 |
-
|
| 118 |
-
def set_selector_from_diag(self, diag: torch.Tensor):
|
| 119 |
-
# diag is a 1D tensor of size (r,)
|
| 120 |
-
assert diag.shape == (self.r,)
|
| 121 |
-
self.selector = nn.Conv2d(
|
| 122 |
-
in_channels=self.r,
|
| 123 |
-
out_channels=self.r,
|
| 124 |
-
kernel_size=1,
|
| 125 |
-
stride=1,
|
| 126 |
-
padding=0,
|
| 127 |
-
bias=False,
|
| 128 |
-
)
|
| 129 |
-
self.selector.weight.data = torch.diag(diag)
|
| 130 |
-
|
| 131 |
-
# same device + dtype as lora_up
|
| 132 |
-
self.selector.weight.data = self.selector.weight.data.to(
|
| 133 |
-
self.lora_up.weight.device
|
| 134 |
-
).to(self.lora_up.weight.dtype)
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vendor/lora/lora_utils.py
DELETED
|
@@ -1,61 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
from torch import nn
|
| 3 |
-
from lora.lora_layers import LoraInjectedLinear, LoraInjectedConv2d
|
| 4 |
-
|
| 5 |
-
def _find_modules(model, ancestor_class=None, search_class=[nn.Linear], exclude_children_of=[LoraInjectedLinear]):
|
| 6 |
-
# Get the targets we should replace all linears under
|
| 7 |
-
if ancestor_class is not None:
|
| 8 |
-
ancestors = (
|
| 9 |
-
module
|
| 10 |
-
for module in model.modules()
|
| 11 |
-
if module.__class__.__name__ in ancestor_class
|
| 12 |
-
)
|
| 13 |
-
else:
|
| 14 |
-
# this, incase you want to naively iterate over all modules.
|
| 15 |
-
ancestors = [module for module in model.modules()]
|
| 16 |
-
|
| 17 |
-
for ancestor in ancestors:
|
| 18 |
-
for fullname, module in ancestor.named_modules():
|
| 19 |
-
# if 'norm1_context' in fullname:
|
| 20 |
-
if any([isinstance(module, _class) for _class in search_class]):
|
| 21 |
-
*path, name = fullname.split(".")
|
| 22 |
-
parent = ancestor
|
| 23 |
-
while path:
|
| 24 |
-
parent = parent.get_submodule(path.pop(0))
|
| 25 |
-
if exclude_children_of and any(
|
| 26 |
-
[isinstance(parent, _class) for _class in exclude_children_of]
|
| 27 |
-
):
|
| 28 |
-
continue
|
| 29 |
-
yield parent, name, module
|
| 30 |
-
|
| 31 |
-
def extract_lora_ups_down(model, target_replace_module={'AdaLayerNormZero'}): # Attention for kv_lora
|
| 32 |
-
|
| 33 |
-
loras = []
|
| 34 |
-
|
| 35 |
-
for _m, _n, _child_module in _find_modules(
|
| 36 |
-
model,
|
| 37 |
-
target_replace_module,
|
| 38 |
-
search_class=[LoraInjectedLinear, LoraInjectedConv2d],
|
| 39 |
-
):
|
| 40 |
-
loras.append((_child_module.lora_up, _child_module.lora_down))
|
| 41 |
-
|
| 42 |
-
if len(loras) == 0:
|
| 43 |
-
raise ValueError("No lora injected.")
|
| 44 |
-
|
| 45 |
-
return loras
|
| 46 |
-
|
| 47 |
-
def save_lora_weight(
|
| 48 |
-
model,
|
| 49 |
-
path="./lora.pt",
|
| 50 |
-
target_replace_module={'AdaLayerNormZero'}, # Attention for kv_lora
|
| 51 |
-
save_half:bool=False
|
| 52 |
-
):
|
| 53 |
-
weights = []
|
| 54 |
-
for _up, _down in extract_lora_ups_down(
|
| 55 |
-
model, target_replace_module=target_replace_module
|
| 56 |
-
):
|
| 57 |
-
dtype = torch.float16 if save_half else torch.float32
|
| 58 |
-
weights.append(_up.weight.to("cpu").to(dtype))
|
| 59 |
-
weights.append(_down.weight.to("cpu").to(dtype))
|
| 60 |
-
|
| 61 |
-
torch.save(weights, path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vendor/osediff_sd3.py
DELETED
|
@@ -1,913 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import sys
|
| 3 |
-
sys.path.append(os.getcwd())
|
| 4 |
-
import yaml
|
| 5 |
-
import copy
|
| 6 |
-
import torch
|
| 7 |
-
import torch.nn as nn
|
| 8 |
-
import torch.nn.functional as F
|
| 9 |
-
from typing import List, Tuple, Optional
|
| 10 |
-
import numpy as np
|
| 11 |
-
import lpips
|
| 12 |
-
from torchvision import transforms
|
| 13 |
-
from PIL import Image
|
| 14 |
-
from peft import LoraConfig, get_peft_model
|
| 15 |
-
|
| 16 |
-
from copy import deepcopy
|
| 17 |
-
from tqdm import tqdm
|
| 18 |
-
|
| 19 |
-
from diffusers import StableDiffusion3Pipeline
|
| 20 |
-
from lora.lora_layers import LoraInjectedLinear, LoraInjectedConv2d
|
| 21 |
-
|
| 22 |
-
from utils.vaehook import VAEHook
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
def inject_lora_vae(vae, lora_rank=4, init_lora_weights="gaussian", verbose=False):
|
| 26 |
-
"""
|
| 27 |
-
Inject LoRA into the VAE's encoder
|
| 28 |
-
"""
|
| 29 |
-
vae.requires_grad_(False)
|
| 30 |
-
vae.train()
|
| 31 |
-
|
| 32 |
-
# Identify modules to LoRA-ify in the encoder
|
| 33 |
-
l_grep = ["conv1", "conv2", "conv_in", "conv_shortcut",
|
| 34 |
-
"conv", "conv_out", "to_k", "to_q", "to_v", "to_out.0"]
|
| 35 |
-
l_target_modules_encoder = []
|
| 36 |
-
for n, p in vae.named_parameters():
|
| 37 |
-
if "bias" in n or "norm" in n:
|
| 38 |
-
continue
|
| 39 |
-
for pattern in l_grep:
|
| 40 |
-
if (pattern in n) and ("encoder" in n):
|
| 41 |
-
l_target_modules_encoder.append(n.replace(".weight", ""))
|
| 42 |
-
elif ("quant_conv" in n) and ("post_quant_conv" not in n):
|
| 43 |
-
l_target_modules_encoder.append(n.replace(".weight", ""))
|
| 44 |
-
|
| 45 |
-
if verbose:
|
| 46 |
-
print("The following VAE parameters will get LoRA:")
|
| 47 |
-
print(l_target_modules_encoder)
|
| 48 |
-
|
| 49 |
-
# Create and add a LoRA adapter
|
| 50 |
-
lora_conf_encoder = LoraConfig(
|
| 51 |
-
r=lora_rank,
|
| 52 |
-
init_lora_weights=init_lora_weights,
|
| 53 |
-
target_modules=l_target_modules_encoder
|
| 54 |
-
)
|
| 55 |
-
|
| 56 |
-
adapter_name = "default_encoder"
|
| 57 |
-
try:
|
| 58 |
-
vae.add_adapter(lora_conf_encoder, adapter_name=adapter_name)
|
| 59 |
-
vae.set_adapter(adapter_name)
|
| 60 |
-
except ValueError as e:
|
| 61 |
-
if "already exists" in str(e):
|
| 62 |
-
print(f"Adapter with name {adapter_name} already exists. Skipping injection.")
|
| 63 |
-
else:
|
| 64 |
-
raise e
|
| 65 |
-
|
| 66 |
-
return vae, l_target_modules_encoder
|
| 67 |
-
|
| 68 |
-
def _find_modules(model, ancestor_class=None, search_class=[nn.Linear], exclude_children_of=[LoraInjectedLinear]):
|
| 69 |
-
# Get the targets we should replace all linears under
|
| 70 |
-
if ancestor_class is not None:
|
| 71 |
-
ancestors = (
|
| 72 |
-
module
|
| 73 |
-
for module in model.modules()
|
| 74 |
-
if module.__class__.__name__ in ancestor_class
|
| 75 |
-
)
|
| 76 |
-
else:
|
| 77 |
-
# this, in case you want to naively iterate over all modules.
|
| 78 |
-
ancestors = [module for module in model.modules()]
|
| 79 |
-
|
| 80 |
-
for ancestor in ancestors:
|
| 81 |
-
for fullname, module in ancestor.named_modules():
|
| 82 |
-
if any([isinstance(module, _class) for _class in search_class]):
|
| 83 |
-
*path, name = fullname.split(".")
|
| 84 |
-
parent = ancestor
|
| 85 |
-
while path:
|
| 86 |
-
parent = parent.get_submodule(path.pop(0))
|
| 87 |
-
if exclude_children_of and any(
|
| 88 |
-
[isinstance(parent, _class) for _class in exclude_children_of]
|
| 89 |
-
):
|
| 90 |
-
continue
|
| 91 |
-
yield parent, name, module
|
| 92 |
-
|
| 93 |
-
def inject_lora(model, ancestor_class, loras=None, r:int=4, dropout_p:float=0.0, scale:float=1.0, verbose:bool=False):
|
| 94 |
-
|
| 95 |
-
model.requires_grad_(False)
|
| 96 |
-
model.train()
|
| 97 |
-
|
| 98 |
-
names = []
|
| 99 |
-
require_grad_params = [] # to be updated
|
| 100 |
-
|
| 101 |
-
total_lora_params = 0
|
| 102 |
-
|
| 103 |
-
if loras is not None:
|
| 104 |
-
loras = torch.load(loras, map_location=model.device, weights_only=True)
|
| 105 |
-
loras = [lora.float() for lora in loras]
|
| 106 |
-
|
| 107 |
-
for _module, name, _child_module in _find_modules(model, ancestor_class): # SiLU + Linear Block
|
| 108 |
-
weight = _child_module.weight
|
| 109 |
-
bias = _child_module.bias
|
| 110 |
-
|
| 111 |
-
if verbose:
|
| 112 |
-
print(f'LoRA Injection : injecting lora into {name}')
|
| 113 |
-
|
| 114 |
-
_tmp = LoraInjectedLinear(
|
| 115 |
-
_child_module.in_features,
|
| 116 |
-
_child_module.out_features,
|
| 117 |
-
_child_module.bias is not None,
|
| 118 |
-
r=r,
|
| 119 |
-
dropout_p=dropout_p,
|
| 120 |
-
scale=scale,
|
| 121 |
-
)
|
| 122 |
-
_tmp.linear.weight = nn.Parameter(weight.float())
|
| 123 |
-
if bias is not None:
|
| 124 |
-
_tmp.linear.bias = nn.Parameter(bias.float())
|
| 125 |
-
|
| 126 |
-
# switch the module
|
| 127 |
-
_tmp.to(device=_child_module.weight.device, dtype=torch.float) # keep as float / mixed precision
|
| 128 |
-
_module._modules[name] = _tmp
|
| 129 |
-
|
| 130 |
-
require_grad_params.append(_module._modules[name].lora_up.parameters())
|
| 131 |
-
require_grad_params.append(_module._modules[name].lora_down.parameters())
|
| 132 |
-
|
| 133 |
-
if loras != None:
|
| 134 |
-
_module._modules[name].lora_up.weight = nn.Parameter(loras.pop(0))
|
| 135 |
-
_module._modules[name].lora_down.weight = nn.Parameter(loras.pop(0))
|
| 136 |
-
|
| 137 |
-
_module._modules[name].lora_up.weight.requires_grad = True
|
| 138 |
-
_module._modules[name].lora_down.weight.requires_grad = True
|
| 139 |
-
names.append(name)
|
| 140 |
-
|
| 141 |
-
if verbose:
|
| 142 |
-
# -------- Count LoRA parameters just added --------
|
| 143 |
-
lora_up_count = sum(p.numel() for p in _tmp.lora_up.parameters())
|
| 144 |
-
lora_down_count = sum(p.numel() for p in _tmp.lora_down.parameters())
|
| 145 |
-
lora_total_for_this_layer = lora_up_count + lora_down_count
|
| 146 |
-
total_lora_params += lora_total_for_this_layer
|
| 147 |
-
print(f" Added {lora_total_for_this_layer} params "
|
| 148 |
-
f"(lora_up={lora_up_count}, lora_down={lora_down_count})")
|
| 149 |
-
|
| 150 |
-
if verbose:
|
| 151 |
-
print(f"Total new LoRA parameters added: {total_lora_params}")
|
| 152 |
-
|
| 153 |
-
return require_grad_params, names
|
| 154 |
-
|
| 155 |
-
def add_mp_hook(transformer):
|
| 156 |
-
'''
|
| 157 |
-
For mixed precision of LoRA. (i.e. keep LoRA as float and others as half)
|
| 158 |
-
'''
|
| 159 |
-
def pre_hook(module, input):
|
| 160 |
-
return input.float()
|
| 161 |
-
|
| 162 |
-
def post_hook(module, input, output):
|
| 163 |
-
return output.half()
|
| 164 |
-
|
| 165 |
-
hooks = []
|
| 166 |
-
for _module, name, _child_module in _find_modules(transformer):
|
| 167 |
-
if isinstance(_child_module, LoraInjectedLinear):
|
| 168 |
-
hook = _child_module.lora_up.register_forward_pre_hook(pre_hook)
|
| 169 |
-
hooks.append(hook)
|
| 170 |
-
hook = _child_module.lora_down.register_forward_hook(post_hook)
|
| 171 |
-
hooks.append(hook)
|
| 172 |
-
|
| 173 |
-
return transformer, hooks
|
| 174 |
-
|
| 175 |
-
def compute_density_for_timestep_sampling(
|
| 176 |
-
weighting_scheme: str, batch_size: int, logit_mean: float = 0.0, logit_std: float = 1.0, mode_scale: Optional[float] = None
|
| 177 |
-
):
|
| 178 |
-
"""
|
| 179 |
-
Compute the density for sampling the timesteps when doing SD3 training.
|
| 180 |
-
|
| 181 |
-
Courtesy: This was contributed by Rafie Walker in https://github.com/huggingface/diffusers/pull/8528.
|
| 182 |
-
|
| 183 |
-
SD3 paper reference: https://arxiv.org/abs/2403.03206v1.
|
| 184 |
-
"""
|
| 185 |
-
if weighting_scheme == "logit_normal":
|
| 186 |
-
# See 3.1 in the SD3 paper ($rf/lognorm(0.00,1.00)$).
|
| 187 |
-
u = torch.normal(mean=logit_mean, std=logit_std, size=(batch_size,), device="cpu")
|
| 188 |
-
u = torch.nn.functional.sigmoid(u)
|
| 189 |
-
elif weighting_scheme == "mode":
|
| 190 |
-
u = torch.rand(size=(batch_size,), device="cpu")
|
| 191 |
-
u = 1 - u - mode_scale * (torch.cos(math.pi * u / 2) ** 2 - 1 + u)
|
| 192 |
-
else:
|
| 193 |
-
u = torch.rand(size=(batch_size,), device="cpu")
|
| 194 |
-
return u
|
| 195 |
-
|
| 196 |
-
def compute_loss_weighting_for_sd3(weighting_scheme: str, sigmas):
|
| 197 |
-
"""
|
| 198 |
-
Computes loss weighting scheme for SD3 training.
|
| 199 |
-
|
| 200 |
-
Courtesy: This was contributed by Rafie Walker in https://github.com/huggingface/diffusers/pull/8528.
|
| 201 |
-
|
| 202 |
-
SD3 paper reference: https://arxiv.org/abs/2403.03206v1.
|
| 203 |
-
"""
|
| 204 |
-
if weighting_scheme == "sigma_sqrt":
|
| 205 |
-
weighting = (sigmas**-2.0).float()
|
| 206 |
-
elif weighting_scheme == "cosmap":
|
| 207 |
-
bot = 1 - 2 * sigmas + 2 * sigmas**2
|
| 208 |
-
weighting = 2 / (math.pi * bot)
|
| 209 |
-
else:
|
| 210 |
-
weighting = torch.ones_like(sigmas)
|
| 211 |
-
return weighting
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
class StableDiffusion3Base():
|
| 215 |
-
def __init__(self, model_key:str='stabilityai/stable-diffusion-3-medium-diffusers', device='cuda', dtype=torch.float16):
|
| 216 |
-
self.device = device
|
| 217 |
-
self.dtype = dtype
|
| 218 |
-
|
| 219 |
-
pipe = StableDiffusion3Pipeline.from_pretrained(model_key, torch_dtype=self.dtype)
|
| 220 |
-
|
| 221 |
-
self.scheduler = pipe.scheduler
|
| 222 |
-
|
| 223 |
-
self.tokenizer_1 = pipe.tokenizer
|
| 224 |
-
self.tokenizer_2 = pipe.tokenizer_2
|
| 225 |
-
self.tokenizer_3 = pipe.tokenizer_3
|
| 226 |
-
self.text_enc_1 = pipe.text_encoder.to(device)
|
| 227 |
-
self.text_enc_2 = pipe.text_encoder_2.to(device)
|
| 228 |
-
self.text_enc_3 = pipe.text_encoder_3.to(device)
|
| 229 |
-
|
| 230 |
-
self.vae=pipe.vae.to(device)
|
| 231 |
-
|
| 232 |
-
self.transformer = pipe.transformer.to(device)
|
| 233 |
-
self.transformer.eval()
|
| 234 |
-
self.transformer.requires_grad_(False)
|
| 235 |
-
|
| 236 |
-
self.vae_scale_factor = (
|
| 237 |
-
2 ** (len(self.vae.config.block_out_channels)-1) if hasattr(self, "vae") and self.vae is not None else 8
|
| 238 |
-
)
|
| 239 |
-
|
| 240 |
-
del pipe
|
| 241 |
-
|
| 242 |
-
def encode_prompt(self, prompt: List[str], batch_size:int=1) -> List[torch.Tensor]:
|
| 243 |
-
'''
|
| 244 |
-
We assume that
|
| 245 |
-
1. number of tokens < max_length
|
| 246 |
-
2. one prompt for one image
|
| 247 |
-
'''
|
| 248 |
-
# CLIP encode (used for modulation of adaLN-zero)
|
| 249 |
-
# now, we have two CLIPs
|
| 250 |
-
text_clip1_ids = self.tokenizer_1(prompt,
|
| 251 |
-
padding="max_length",
|
| 252 |
-
max_length=77,
|
| 253 |
-
truncation=True,
|
| 254 |
-
return_tensors='pt').input_ids
|
| 255 |
-
text_clip1_emb = self.text_enc_1(text_clip1_ids.to(self.device), output_hidden_states=True)
|
| 256 |
-
pool_clip1_emb = text_clip1_emb[0].to(dtype=self.dtype, device=self.device)
|
| 257 |
-
text_clip1_emb = text_clip1_emb.hidden_states[-2].to(dtype=self.dtype, device=self.device)
|
| 258 |
-
|
| 259 |
-
text_clip2_ids = self.tokenizer_2(prompt,
|
| 260 |
-
padding="max_length",
|
| 261 |
-
max_length=77,
|
| 262 |
-
truncation=True,
|
| 263 |
-
return_tensors='pt').input_ids
|
| 264 |
-
text_clip2_emb = self.text_enc_2(text_clip2_ids.to(self.device), output_hidden_states=True)
|
| 265 |
-
pool_clip2_emb = text_clip2_emb[0].to(dtype=self.dtype, device=self.device)
|
| 266 |
-
text_clip2_emb = text_clip2_emb.hidden_states[-2].to(dtype=self.dtype, device=self.device)
|
| 267 |
-
|
| 268 |
-
# T5 encode (used for text condition)
|
| 269 |
-
text_t5_ids = self.tokenizer_3(prompt,
|
| 270 |
-
padding="max_length",
|
| 271 |
-
max_length=512,
|
| 272 |
-
truncation=True,
|
| 273 |
-
add_special_tokens=True,
|
| 274 |
-
return_tensors='pt').input_ids
|
| 275 |
-
text_t5_emb = self.text_enc_3(text_t5_ids.to(self.device))[0]
|
| 276 |
-
text_t5_emb = text_t5_emb.to(dtype=self.dtype, device=self.device)
|
| 277 |
-
|
| 278 |
-
# Merge
|
| 279 |
-
clip_prompt_emb = torch.cat([text_clip1_emb, text_clip2_emb], dim=-1)
|
| 280 |
-
clip_prompt_emb = torch.nn.functional.pad(
|
| 281 |
-
clip_prompt_emb, (0, text_t5_emb.shape[-1] - clip_prompt_emb.shape[-1])
|
| 282 |
-
)
|
| 283 |
-
prompt_emb = torch.cat([clip_prompt_emb, text_t5_emb], dim=-2)
|
| 284 |
-
pooled_prompt_emb = torch.cat([pool_clip1_emb, pool_clip2_emb], dim=-1)
|
| 285 |
-
|
| 286 |
-
return prompt_emb, pooled_prompt_emb
|
| 287 |
-
|
| 288 |
-
def initialize_latent(self, img_size:Tuple[int], batch_size:int=1, **kwargs):
|
| 289 |
-
H, W = img_size
|
| 290 |
-
lH, lW = H//self.vae_scale_factor, W//self.vae_scale_factor
|
| 291 |
-
lC = self.transformer.config.in_channels
|
| 292 |
-
latent_shape = (batch_size, lC, lH, lW)
|
| 293 |
-
|
| 294 |
-
z = torch.randn(latent_shape, device=self.device, dtype=self.dtype)
|
| 295 |
-
|
| 296 |
-
return z
|
| 297 |
-
|
| 298 |
-
def encode(self, image: torch.Tensor) -> torch.Tensor:
|
| 299 |
-
z = self.vae.encode(image).latent_dist.sample()
|
| 300 |
-
z = (z-self.vae.config.shift_factor) * self.vae.config.scaling_factor
|
| 301 |
-
return z
|
| 302 |
-
|
| 303 |
-
def decode(self, z: torch.Tensor) -> torch.Tensor:
|
| 304 |
-
z = (z/self.vae.config.scaling_factor) + self.vae.config.shift_factor
|
| 305 |
-
return self.vae.decode(z, return_dict=False)[0]
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
class SD3Euler(StableDiffusion3Base):
|
| 309 |
-
def __init__(self, model_key:str='stabilityai/stable-diffusion-3-medium-diffusers', device='cuda'):
|
| 310 |
-
super().__init__(model_key=model_key, device=device)
|
| 311 |
-
|
| 312 |
-
def inversion(self, src_img, prompts: List[str], NFE:int, cfg_scale: float=1.0, batch_size: int=1):
|
| 313 |
-
|
| 314 |
-
# encode text prompts
|
| 315 |
-
prompt_emb, pooled_emb = self.encode_prompt(prompts, batch_size)
|
| 316 |
-
null_prompt_emb, null_pooled_emb = self.encode_prompt([""], batch_size)
|
| 317 |
-
|
| 318 |
-
# initialize latent
|
| 319 |
-
src_img = src_img.to(device=self.device, dtype=self.dtype)
|
| 320 |
-
with torch.no_grad():
|
| 321 |
-
z = self.encode(src_img)
|
| 322 |
-
z0 = z.clone()
|
| 323 |
-
|
| 324 |
-
# timesteps (default option. You can make your custom here.)
|
| 325 |
-
self.scheduler.set_timesteps(NFE, device=self.device)
|
| 326 |
-
timesteps = self.scheduler.timesteps
|
| 327 |
-
timesteps = torch.cat([timesteps, torch.zeros(1, device=self.device)])
|
| 328 |
-
timesteps = reversed(timesteps)
|
| 329 |
-
sigmas = timesteps / self.scheduler.config.num_train_timesteps
|
| 330 |
-
|
| 331 |
-
# Solve ODE
|
| 332 |
-
pbar = tqdm(timesteps[:-1], total=NFE, desc='SD3 Euler Inversion')
|
| 333 |
-
for i, t in enumerate(pbar):
|
| 334 |
-
timestep = t.expand(z.shape[0]).to(self.device)
|
| 335 |
-
pred_v = self.predict_vector(z, timestep, prompt_emb, pooled_emb)
|
| 336 |
-
if cfg_scale != 1.0:
|
| 337 |
-
pred_null_v = self.predict_vector(z, timestep, null_prompt_emb, null_pooled_emb)
|
| 338 |
-
else:
|
| 339 |
-
pred_null_v = 0.0
|
| 340 |
-
|
| 341 |
-
sigma = sigmas[i]
|
| 342 |
-
sigma_next = sigmas[i+1]
|
| 343 |
-
|
| 344 |
-
z = z + (sigma_next - sigma) * (pred_null_v + cfg_scale * (pred_v - pred_null_v))
|
| 345 |
-
|
| 346 |
-
return z
|
| 347 |
-
|
| 348 |
-
def sample(self, prompts: List[str], NFE:int, img_shape: Optional[Tuple[int]]=None, cfg_scale: float=1.0, batch_size: int = 1, latent:Optional[torch.Tensor]=None):
|
| 349 |
-
imgH, imgW = img_shape if img_shape is not None else (512, 512)
|
| 350 |
-
|
| 351 |
-
# encode text prompts
|
| 352 |
-
with torch.no_grad():
|
| 353 |
-
prompt_emb, pooled_emb = self.encode_prompt(prompts, batch_size)
|
| 354 |
-
null_prompt_emb, null_pooled_emb = self.encode_prompt([""], batch_size)
|
| 355 |
-
|
| 356 |
-
# initialize latent
|
| 357 |
-
if latent is None:
|
| 358 |
-
z = self.initialize_latent((imgH, imgW), batch_size)
|
| 359 |
-
else:
|
| 360 |
-
z = latent
|
| 361 |
-
|
| 362 |
-
# timesteps (default option. You can make your custom here.)
|
| 363 |
-
self.scheduler.set_timesteps(NFE, device=self.device)
|
| 364 |
-
timesteps = self.scheduler.timesteps
|
| 365 |
-
sigmas = timesteps / self.scheduler.config.num_train_timesteps
|
| 366 |
-
|
| 367 |
-
# Solve ODE
|
| 368 |
-
pbar = tqdm(timesteps, total=NFE, desc='SD3 Euler')
|
| 369 |
-
for i, t in enumerate(pbar):
|
| 370 |
-
timestep = t.expand(z.shape[0]).to(self.device)
|
| 371 |
-
pred_v = self.predict_vector(z, timestep, prompt_emb, pooled_emb)
|
| 372 |
-
if cfg_scale != 1.0:
|
| 373 |
-
pred_null_v = self.predict_vector(z, timestep, null_prompt_emb, null_pooled_emb)
|
| 374 |
-
else:
|
| 375 |
-
pred_null_v = 0.0
|
| 376 |
-
|
| 377 |
-
sigma = sigmas[i]
|
| 378 |
-
sigma_next = sigmas[i+1] if i+1 < NFE else 0.0
|
| 379 |
-
|
| 380 |
-
z = z + (sigma_next - sigma) * (pred_null_v + cfg_scale * (pred_v - pred_null_v))
|
| 381 |
-
|
| 382 |
-
# decode
|
| 383 |
-
with torch.no_grad():
|
| 384 |
-
img = self.decode(z)
|
| 385 |
-
return img
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
class OSEDiff_SD3_GEN(torch.nn.Module):
|
| 389 |
-
def __init__(self, args, base_model):
|
| 390 |
-
super().__init__()
|
| 391 |
-
|
| 392 |
-
self.args = args
|
| 393 |
-
self.model = base_model
|
| 394 |
-
|
| 395 |
-
# Add lora to transformer
|
| 396 |
-
print('Adding LoRA to OSEDiff_SD3_GEN')
|
| 397 |
-
self.transformer_gen = copy.deepcopy(self.model.transformer)
|
| 398 |
-
self.transformer_gen.to('cuda:1')
|
| 399 |
-
|
| 400 |
-
self.transformer_gen.requires_grad_(False)
|
| 401 |
-
self.transformer_gen.train()
|
| 402 |
-
self.transformer_gen, hooks = add_mp_hook(self.transformer_gen)
|
| 403 |
-
self.hooks = hooks
|
| 404 |
-
|
| 405 |
-
lora_params, _ = inject_lora(self.transformer_gen, {"AdaLayerNormZero"}, r=args.lora_rank, verbose=False)
|
| 406 |
-
for name, param in self.transformer_gen.named_parameters():
|
| 407 |
-
if "lora_" in name:
|
| 408 |
-
param.requires_grad = True # LoRA up/down
|
| 409 |
-
else:
|
| 410 |
-
param.requires_grad = False # everything else
|
| 411 |
-
|
| 412 |
-
# Insert LoRA into VAE
|
| 413 |
-
print("Adding LoRA to VAE")
|
| 414 |
-
self.model.vae, self.lora_vae_modules_encoder = inject_lora_vae(self.model.vae, lora_rank=args.lora_rank, verbose=False)
|
| 415 |
-
|
| 416 |
-
def predict_vector(self, z, t, prompt_emb, pooled_emb):
|
| 417 |
-
v = self.transformer_gen(hidden_states=z,
|
| 418 |
-
timestep=t,
|
| 419 |
-
pooled_projections=pooled_emb,
|
| 420 |
-
encoder_hidden_states=prompt_emb,
|
| 421 |
-
return_dict=False)[0]
|
| 422 |
-
return v
|
| 423 |
-
|
| 424 |
-
def forward(self, x_src, batch=None, args=None):
|
| 425 |
-
|
| 426 |
-
z_src = self.model.encode(x_src.to(dtype=torch.float32, device=self.model.vae.device))
|
| 427 |
-
z_src = z_src.to(self.transformer_gen.device)
|
| 428 |
-
|
| 429 |
-
# calculate prompt_embeddings and neg_prompt_embeddings
|
| 430 |
-
batch_size, _, _, _ = x_src.shape
|
| 431 |
-
with torch.no_grad():
|
| 432 |
-
prompt_embeds, pooled_embeds = self.model.encode_prompt(batch["prompt"], batch_size)
|
| 433 |
-
neg_prompt_embeds, neg_pooled_embeds = self.model.encode_prompt(batch["neg_prompt"], batch_size)
|
| 434 |
-
|
| 435 |
-
NFE = 1
|
| 436 |
-
self.model.scheduler.set_timesteps(NFE, device=self.model.device)
|
| 437 |
-
timesteps = self.model.scheduler.timesteps
|
| 438 |
-
sigmas = timesteps / self.model.scheduler.config.num_train_timesteps
|
| 439 |
-
sigmas = sigmas.to(self.transformer_gen.device)
|
| 440 |
-
|
| 441 |
-
# Solve ODE
|
| 442 |
-
i = 0
|
| 443 |
-
t = timesteps[0]
|
| 444 |
-
|
| 445 |
-
timestep = t.expand(z_src.shape[0]).to(self.transformer_gen.device)
|
| 446 |
-
prompt_embeds = prompt_embeds.to(self.transformer_gen.device, dtype=torch.float32)
|
| 447 |
-
pooled_embeds = pooled_embeds.to(self.transformer_gen.device, dtype=torch.float32)
|
| 448 |
-
pred_v = self.predict_vector(z_src, timestep, prompt_embeds, pooled_embeds)
|
| 449 |
-
pred_null_v = 0.0
|
| 450 |
-
|
| 451 |
-
sigma = sigmas[i]
|
| 452 |
-
sigma_next = sigmas[i+1] if i+1 < NFE else 0.0
|
| 453 |
-
|
| 454 |
-
z_src = z_src + (sigma_next - sigma) * (pred_null_v + 1 * (pred_v - pred_null_v))
|
| 455 |
-
|
| 456 |
-
output_image = self.model.decode(z_src.to(dtype=torch.float32, device=self.model.vae.device))
|
| 457 |
-
|
| 458 |
-
return output_image, z_src, prompt_embeds, pooled_embeds
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
class OSEDiff_SD3_REG(torch.nn.Module):
|
| 462 |
-
def __init__(self, args, base_model):
|
| 463 |
-
super().__init__()
|
| 464 |
-
|
| 465 |
-
self.args = args
|
| 466 |
-
self.model = base_model
|
| 467 |
-
self.transformer_org = self.model.transformer
|
| 468 |
-
|
| 469 |
-
# Add lora to transformer
|
| 470 |
-
print('Adding LoRA to OSEDiff_SD3_REG')
|
| 471 |
-
self.transformer_reg = copy.deepcopy(self.transformer_org)
|
| 472 |
-
self.transformer_reg.to('cuda:1')
|
| 473 |
-
|
| 474 |
-
self.transformer_reg.requires_grad_(False)
|
| 475 |
-
self.transformer_reg.train()
|
| 476 |
-
self.transformer_reg, hooks = add_mp_hook(self.transformer_reg)
|
| 477 |
-
self.hooks = hooks
|
| 478 |
-
|
| 479 |
-
lora_params, _ = inject_lora(self.transformer_reg, {"AdaLayerNormZero"}, r=args.lora_rank, verbose=False)
|
| 480 |
-
for name, param in self.transformer_reg.named_parameters():
|
| 481 |
-
if "lora_" in name:
|
| 482 |
-
param.requires_grad = True # LoRA up/down
|
| 483 |
-
else:
|
| 484 |
-
param.requires_grad = False # everything else
|
| 485 |
-
|
| 486 |
-
def predict_vector_reg(self, z, t, prompt_emb, pooled_emb):
|
| 487 |
-
v = self.transformer_reg(hidden_states=z,
|
| 488 |
-
timestep=t,
|
| 489 |
-
pooled_projections=pooled_emb,
|
| 490 |
-
encoder_hidden_states=prompt_emb,
|
| 491 |
-
return_dict=False)[0]
|
| 492 |
-
return v
|
| 493 |
-
|
| 494 |
-
def predict_vector_org(self, z, t, prompt_emb, pooled_emb):
|
| 495 |
-
v = self.transformer_org(hidden_states=z,
|
| 496 |
-
timestep=t,
|
| 497 |
-
pooled_projections=pooled_emb,
|
| 498 |
-
encoder_hidden_states=prompt_emb,
|
| 499 |
-
return_dict=False)[0]
|
| 500 |
-
return v
|
| 501 |
-
|
| 502 |
-
def distribution_matching_loss(self, z0, prompt_embeds, pooled_embeds, global_step, args):
|
| 503 |
-
|
| 504 |
-
with torch.no_grad():
|
| 505 |
-
device = self.transformer_reg.device
|
| 506 |
-
# get timesteps and sigma
|
| 507 |
-
u = compute_density_for_timestep_sampling(
|
| 508 |
-
weighting_scheme="uniform",
|
| 509 |
-
batch_size=1,
|
| 510 |
-
logit_mean=0.0,
|
| 511 |
-
logit_std=1.0,
|
| 512 |
-
mode_scale=1.29,
|
| 513 |
-
)
|
| 514 |
-
|
| 515 |
-
t_idx = (u*1000).long().to(device)
|
| 516 |
-
self.model.scheduler.set_timesteps(1000, device=device)
|
| 517 |
-
times = self.model.scheduler.timesteps
|
| 518 |
-
t = times[t_idx]
|
| 519 |
-
sigma = t / 1000
|
| 520 |
-
|
| 521 |
-
# get noise and xt
|
| 522 |
-
z0 = z0.to(device)
|
| 523 |
-
noise = torch.randn_like(z0)
|
| 524 |
-
sigma = sigma.half()
|
| 525 |
-
zt = (1-sigma) * z0 + sigma * noise
|
| 526 |
-
|
| 527 |
-
# Get x0_prediction of transformer_reg
|
| 528 |
-
v_pred_reg = self.predict_vector_reg(zt, t, prompt_embeds.to(device), pooled_embeds.to(device))
|
| 529 |
-
reg_model_pred = v_pred_reg * (-sigma) + zt # this is x0_prediction for reg
|
| 530 |
-
|
| 531 |
-
# Get x0_prediction of transformer_org
|
| 532 |
-
org_device = self.transformer_org.device
|
| 533 |
-
v_pred_org = self.predict_vector_org(zt.to(org_device), t.to(org_device), prompt_embeds.to(org_device), pooled_embeds.to(org_device))
|
| 534 |
-
org_model_pred = v_pred_org * (-sigma.to(org_device)) + zt.to(org_device) # this is x0_prediction for org
|
| 535 |
-
|
| 536 |
-
# Visualization
|
| 537 |
-
if global_step % 100 == 1:
|
| 538 |
-
self.vsd_visualization(z0, noise, zt, reg_model_pred, org_model_pred, global_step, args)
|
| 539 |
-
|
| 540 |
-
weighting_factor = torch.abs(z0 - org_model_pred.to(device)).mean(dim=[1, 2, 3], keepdim=True)
|
| 541 |
-
|
| 542 |
-
grad = (reg_model_pred - org_model_pred.to(device)) / weighting_factor
|
| 543 |
-
loss = F.mse_loss(z0, (z0 - grad).detach())
|
| 544 |
-
|
| 545 |
-
return loss
|
| 546 |
-
|
| 547 |
-
def vsd_visualization(self, z0, noise, zt, reg_model_pred, org_model_pred, global_step, args):
|
| 548 |
-
#-------- Visualization --------#
|
| 549 |
-
# 1. Visualize latents, noise, zt
|
| 550 |
-
z0_img = self.model.decode(z0.to(dtype=torch.float32, device=self.model.vae.device))
|
| 551 |
-
ns_img = self.model.decode(noise.to(dtype=torch.float32, device=self.model.vae.device))
|
| 552 |
-
zt_img = self.model.decode(zt.to(dtype=torch.float32, device=self.model.vae.device))
|
| 553 |
-
|
| 554 |
-
z0_img_pil = transforms.ToPILImage()(torch.clamp(z0_img[0].cpu(), -1.0, 1.0) * 0.5 + 0.5)
|
| 555 |
-
ns_img_pil = transforms.ToPILImage()(torch.clamp(ns_img[0].cpu(), -1.0, 1.0) * 0.5 + 0.5)
|
| 556 |
-
zt_img_pil = transforms.ToPILImage()(torch.clamp(zt_img[0].cpu(), -1.0, 1.0) * 0.5 + 0.5)
|
| 557 |
-
|
| 558 |
-
# 2. Visualize reg_img, org_img
|
| 559 |
-
reg_img = self.model.decode(reg_model_pred.to(dtype=torch.float32, device=self.model.vae.device))
|
| 560 |
-
org_img = self.model.decode(org_model_pred.to(dtype=torch.float32, device=self.model.vae.device))
|
| 561 |
-
|
| 562 |
-
reg_img_pil = transforms.ToPILImage()(torch.clamp(reg_img[0].cpu(), -1.0, 1.0) * 0.5 + 0.5)
|
| 563 |
-
org_img_pil = transforms.ToPILImage()(torch.clamp(org_img[0].cpu(), -1.0, 1.0) * 0.5 + 0.5)
|
| 564 |
-
|
| 565 |
-
# Concatenate images side by side
|
| 566 |
-
w, h = z0_img_pil.width, z0_img_pil.height
|
| 567 |
-
combined_image = Image.new('RGB', (w*5, h))
|
| 568 |
-
combined_image.paste(z0_img_pil, (0, 0))
|
| 569 |
-
combined_image.paste(ns_img_pil, (w, 0))
|
| 570 |
-
combined_image.paste(zt_img_pil, (w*2, 0))
|
| 571 |
-
combined_image.paste(reg_img_pil, (w*3, 0))
|
| 572 |
-
combined_image.paste(org_img_pil, (w*4, 0))
|
| 573 |
-
combined_image.save(os.path.join(args.output_dir, f'visualization/vsd/{global_step}.png'))
|
| 574 |
-
#-------- Visualization --------#
|
| 575 |
-
|
| 576 |
-
def diff_loss(self, z0, prompt_embeds, pooled_embeds, net_lpips, args):
|
| 577 |
-
|
| 578 |
-
device = self.transformer_reg.device
|
| 579 |
-
u = compute_density_for_timestep_sampling(
|
| 580 |
-
weighting_scheme="uniform",
|
| 581 |
-
batch_size=1,
|
| 582 |
-
logit_mean=0.0,
|
| 583 |
-
logit_std=1.0,
|
| 584 |
-
mode_scale=1.29,
|
| 585 |
-
)
|
| 586 |
-
|
| 587 |
-
t_idx = (u*1000).long().to(device)
|
| 588 |
-
self.model.scheduler.set_timesteps(1000, device=device)
|
| 589 |
-
times = self.model.scheduler.timesteps
|
| 590 |
-
t = times[t_idx]
|
| 591 |
-
sigma = t / 1000
|
| 592 |
-
|
| 593 |
-
z0 = z0.to(device)
|
| 594 |
-
z0, prompt_embeds = z0.detach(), prompt_embeds.detach()
|
| 595 |
-
noise = torch.randn_like(z0)
|
| 596 |
-
sigma = sigma.half()
|
| 597 |
-
zt = (1-sigma) * z0 + sigma * noise # noisy latents
|
| 598 |
-
|
| 599 |
-
# v-prediction
|
| 600 |
-
v_pred = self.predict_vector_reg(zt, t, prompt_embeds.to(device), pooled_embeds.to(device))
|
| 601 |
-
model_pred = v_pred * (-sigma) + zt
|
| 602 |
-
target = z0
|
| 603 |
-
|
| 604 |
-
loss_weight = compute_loss_weighting_for_sd3("logit_normal", sigma)
|
| 605 |
-
diffusion_loss = loss_weight.float() * F.mse_loss(model_pred.float(), target.float())
|
| 606 |
-
|
| 607 |
-
loss_d = diffusion_loss
|
| 608 |
-
|
| 609 |
-
return loss_d.mean()
|
| 610 |
-
|
| 611 |
-
class OSEDiff_SD3_TEST(torch.nn.Module):
|
| 612 |
-
def __init__(self, args, base_model):
|
| 613 |
-
super().__init__()
|
| 614 |
-
|
| 615 |
-
self.args = args
|
| 616 |
-
self.model = base_model
|
| 617 |
-
self.lora_path = args.lora_path
|
| 618 |
-
self.vae_path = args.vae_path
|
| 619 |
-
|
| 620 |
-
# Add lora to transformer
|
| 621 |
-
print(f'Loading LoRA to Transformer from {self.lora_path}')
|
| 622 |
-
self.model.transformer.requires_grad_(False)
|
| 623 |
-
lora_params, _ = inject_lora(self.model.transformer, {"AdaLayerNormZero"}, loras=self.lora_path, r=args.lora_rank, verbose=False)
|
| 624 |
-
for name, param in self.model.transformer.named_parameters():
|
| 625 |
-
param.requires_grad = False
|
| 626 |
-
|
| 627 |
-
# Insert LoRA into VAE
|
| 628 |
-
print(f"Loading LoRA to VAE from {self.vae_path}")
|
| 629 |
-
self.model.vae, self.lora_vae_modules_encoder = inject_lora_vae(self.model.vae, lora_rank=args.lora_rank, verbose=False)
|
| 630 |
-
encoder_state_dict_fp16 = torch.load(self.vae_path, map_location="cpu", weights_only=True)
|
| 631 |
-
self.model.vae.encoder.load_state_dict(encoder_state_dict_fp16)
|
| 632 |
-
|
| 633 |
-
def predict_vector(self, z, t, prompt_emb, pooled_emb):
|
| 634 |
-
v = self.model.transformer(hidden_states=z,
|
| 635 |
-
timestep=t,
|
| 636 |
-
pooled_projections=pooled_emb,
|
| 637 |
-
encoder_hidden_states=prompt_emb,
|
| 638 |
-
return_dict=False)[0]
|
| 639 |
-
return v
|
| 640 |
-
|
| 641 |
-
@torch.no_grad()
|
| 642 |
-
def forward(self, x_src, prompt):
|
| 643 |
-
|
| 644 |
-
z_src = self.model.vae.encode(x_src.to(dtype=torch.float32, device=self.model.vae.device)).latent_dist.sample() * self.model.vae.config.scaling_factor
|
| 645 |
-
|
| 646 |
-
z_src = z_src.to(self.model.transformer.device)
|
| 647 |
-
|
| 648 |
-
# calculate prompt_embeddings and neg_prompt_embeddings
|
| 649 |
-
batch_size, _, _, _ = x_src.shape
|
| 650 |
-
with torch.no_grad():
|
| 651 |
-
prompt_embeds, pooled_embeds = self.model.encode_prompt([prompt], batch_size)
|
| 652 |
-
|
| 653 |
-
self.model.scheduler.set_timesteps(1, device=self.model.device)
|
| 654 |
-
timesteps = self.model.scheduler.timesteps
|
| 655 |
-
|
| 656 |
-
# Solve ODE
|
| 657 |
-
t = timesteps[0]
|
| 658 |
-
timestep = t.expand(z_src.shape[0]).to(self.model.transformer.device)
|
| 659 |
-
prompt_embeds = prompt_embeds.to(self.model.transformer.device, dtype=torch.float32)
|
| 660 |
-
pooled_embeds = pooled_embeds.to(self.model.transformer.device, dtype=torch.float32)
|
| 661 |
-
pred_v = self.predict_vector(z_src, timestep, prompt_embeds, pooled_embeds)
|
| 662 |
-
|
| 663 |
-
z_src = z_src - pred_v
|
| 664 |
-
|
| 665 |
-
with torch.no_grad():
|
| 666 |
-
output_image = self.model.decode(z_src.to(dtype=torch.float32, device=self.model.vae.device))
|
| 667 |
-
|
| 668 |
-
return output_image
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
class OSEDiff_SD3_TEST_efficient(torch.nn.Module):
|
| 672 |
-
def __init__(self, args, base_model):
|
| 673 |
-
super().__init__()
|
| 674 |
-
|
| 675 |
-
self.args = args
|
| 676 |
-
self.model = base_model
|
| 677 |
-
self.lora_path = args.lora_path
|
| 678 |
-
self.vae_path = args.vae_path
|
| 679 |
-
|
| 680 |
-
# Add lora to transformer
|
| 681 |
-
print(f'Loading LoRA to Transformer from {self.lora_path}')
|
| 682 |
-
self.model.transformer.requires_grad_(False)
|
| 683 |
-
lora_params, _ = inject_lora(self.model.transformer, {"AdaLayerNormZero"}, loras=self.lora_path, r=args.lora_rank, verbose=False)
|
| 684 |
-
for name, param in self.model.transformer.named_parameters():
|
| 685 |
-
param.requires_grad = False
|
| 686 |
-
|
| 687 |
-
# Insert LoRA into VAE
|
| 688 |
-
print(f"Loading LoRA to VAE from {self.vae_path}")
|
| 689 |
-
self.model.vae, self.lora_vae_modules_encoder = inject_lora_vae(self.model.vae, lora_rank=args.lora_rank, verbose=False)
|
| 690 |
-
encoder_state_dict_fp16 = torch.load(self.vae_path, map_location="cpu", weights_only=True)
|
| 691 |
-
self.model.vae.encoder.load_state_dict(encoder_state_dict_fp16)
|
| 692 |
-
|
| 693 |
-
def predict_vector(self, z, t, prompt_emb, pooled_emb):
|
| 694 |
-
v = self.model.transformer(hidden_states=z,
|
| 695 |
-
timestep=t,
|
| 696 |
-
pooled_projections=pooled_emb,
|
| 697 |
-
encoder_hidden_states=prompt_emb,
|
| 698 |
-
return_dict=False)[0]
|
| 699 |
-
return v
|
| 700 |
-
|
| 701 |
-
@torch.no_grad()
|
| 702 |
-
def forward(self, x_src, prompt):
|
| 703 |
-
|
| 704 |
-
z_src = self.model.vae.encode(x_src.to(dtype=torch.float32, device=self.model.vae.device)).latent_dist.sample() * self.model.vae.config.scaling_factor
|
| 705 |
-
|
| 706 |
-
z_src = z_src.to(self.model.transformer.device)
|
| 707 |
-
|
| 708 |
-
# calculate prompt_embeddings
|
| 709 |
-
batch_size, _, _, _ = x_src.shape
|
| 710 |
-
prompt_embeds, pooled_embeds = self.model.encode_prompt([prompt], batch_size)
|
| 711 |
-
|
| 712 |
-
self.model.scheduler.set_timesteps(1, device=self.model.device)
|
| 713 |
-
timesteps = self.model.scheduler.timesteps
|
| 714 |
-
|
| 715 |
-
# Solve ODE
|
| 716 |
-
t = timesteps[0]
|
| 717 |
-
timestep = t.expand(z_src.shape[0]).to(self.model.transformer.device)
|
| 718 |
-
prompt_embeds = prompt_embeds.to(self.model.transformer.device, dtype=torch.float32)
|
| 719 |
-
pooled_embeds = pooled_embeds.to(self.model.transformer.device, dtype=torch.float32)
|
| 720 |
-
pred_v = self.predict_vector(z_src, timestep, prompt_embeds, pooled_embeds)
|
| 721 |
-
z_src = z_src - pred_v
|
| 722 |
-
|
| 723 |
-
output_image = self.model.decode(z_src.to(dtype=torch.float32, device=self.model.vae.device))
|
| 724 |
-
|
| 725 |
-
return output_image
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
class OSEDiff_SD3_TEST_TILE(torch.nn.Module):
|
| 729 |
-
def __init__(self, args, base_model):
|
| 730 |
-
super().__init__()
|
| 731 |
-
|
| 732 |
-
self.args = args
|
| 733 |
-
self.model = base_model
|
| 734 |
-
self.lora_path = args.lora_path
|
| 735 |
-
self.vae_path = args.vae_path
|
| 736 |
-
|
| 737 |
-
# Add lora to transformer
|
| 738 |
-
print(f'Loading LoRA to Transformer from {self.lora_path}')
|
| 739 |
-
self.model.transformer.requires_grad_(False)
|
| 740 |
-
lora_params, _ = inject_lora(self.model.transformer, {"AdaLayerNormZero"}, loras=self.lora_path, r=args.lora_rank, verbose=False)
|
| 741 |
-
for name, param in self.model.transformer.named_parameters():
|
| 742 |
-
param.requires_grad = False
|
| 743 |
-
|
| 744 |
-
# Insert LoRA into VAE
|
| 745 |
-
print(f"Loading LoRA to VAE from {self.vae_path}")
|
| 746 |
-
self.model.vae, self.lora_vae_modules_encoder = inject_lora_vae(self.model.vae, lora_rank=args.lora_rank, verbose=False)
|
| 747 |
-
encoder_state_dict_fp16 = torch.load(self.vae_path, map_location="cpu", weights_only=True)
|
| 748 |
-
self.model.vae.encoder.load_state_dict(encoder_state_dict_fp16)
|
| 749 |
-
|
| 750 |
-
# save original forward (only once)
|
| 751 |
-
if not hasattr(self.model.vae.encoder, 'original_forward'):
|
| 752 |
-
setattr(self.model.vae.encoder, 'original_forward', self.model.vae.encoder.forward)
|
| 753 |
-
if not hasattr(self.model.vae.decoder, 'original_forward'):
|
| 754 |
-
setattr(self.model.vae.decoder, 'original_forward', self.model.vae.decoder.forward)
|
| 755 |
-
encoder_tile = args.vae_encoder_tiled_size
|
| 756 |
-
decoder_tile = args.vae_decoder_tiled_size
|
| 757 |
-
self.model.vae.encoder.forward = VAEHook(
|
| 758 |
-
self.model.vae.encoder,
|
| 759 |
-
tile_size=encoder_tile,
|
| 760 |
-
is_decoder=False,
|
| 761 |
-
fast_decoder=False,
|
| 762 |
-
fast_encoder=True,
|
| 763 |
-
color_fix=False,
|
| 764 |
-
to_gpu=True
|
| 765 |
-
)
|
| 766 |
-
self.model.vae.decoder.forward = VAEHook(
|
| 767 |
-
self.model.vae.decoder,
|
| 768 |
-
tile_size=decoder_tile,
|
| 769 |
-
is_decoder=True,
|
| 770 |
-
fast_decoder=True,
|
| 771 |
-
fast_encoder=False,
|
| 772 |
-
color_fix=False,
|
| 773 |
-
to_gpu=True
|
| 774 |
-
)
|
| 775 |
-
|
| 776 |
-
def predict_vector(self, z, t, prompt_emb, pooled_emb):
|
| 777 |
-
v = self.model.transformer(hidden_states=z,
|
| 778 |
-
timestep=t,
|
| 779 |
-
pooled_projections=pooled_emb,
|
| 780 |
-
encoder_hidden_states=prompt_emb,
|
| 781 |
-
return_dict=False)[0]
|
| 782 |
-
return v
|
| 783 |
-
|
| 784 |
-
@torch.no_grad()
|
| 785 |
-
def create_full_latent(self, x_full: torch.Tensor, vlm_model, vlm_processor, full_path, next_path, prompt_type) -> torch.Tensor:
|
| 786 |
-
device = self.model.transformer.device
|
| 787 |
-
# 1) encode to full latent (via VAEHook)
|
| 788 |
-
z_full = self.model.vae.encode(x_full).latent_dist.sample() \
|
| 789 |
-
* self.model.vae.config.scaling_factor
|
| 790 |
-
z_full = z_full.to(device)
|
| 791 |
-
B, C, H, W = z_full.shape
|
| 792 |
-
|
| 793 |
-
# 2) grid size
|
| 794 |
-
tsize = self.args.latent_tiled_size
|
| 795 |
-
tover = self.args.latent_tiled_overlap
|
| 796 |
-
stride = tsize - tover
|
| 797 |
-
rows = (H - tsize + stride - 1)//stride + 1
|
| 798 |
-
cols = (W - tsize + stride - 1)//stride + 1
|
| 799 |
-
print(f'TILE SIZE: {tsize}, TILE OVERLAP: {tover}, STRIDE: {stride}, ROWS: {rows}, COLS: {cols}')
|
| 800 |
-
|
| 801 |
-
# 3) make gaussian weight patched [B,C,tsize,tsize]
|
| 802 |
-
weights = self._make_gaussian(tsize, tsize, 1).to(z_full.device)
|
| 803 |
-
|
| 804 |
-
# 4) collect all patches
|
| 805 |
-
positions = []
|
| 806 |
-
out_tiles = []
|
| 807 |
-
timestep = self.model.scheduler.timesteps[0].expand(B).to(z_full.device)
|
| 808 |
-
|
| 809 |
-
for i in range(rows):
|
| 810 |
-
y0 = min(i*stride, H - tsize)
|
| 811 |
-
for j in range(cols):
|
| 812 |
-
x0 = min(j*stride, W - tsize)
|
| 813 |
-
positions.append((y0, x0))
|
| 814 |
-
patch = z_full[:, :, y0:y0+tsize, x0:x0+tsize]
|
| 815 |
-
|
| 816 |
-
# decode and save patch for later usage
|
| 817 |
-
patch_path = f'{next_path[:-4]}_patch_row{i}col{j}.png'
|
| 818 |
-
patch_img = self.decode_full_latent(patch)
|
| 819 |
-
patch_pil = transforms.ToPILImage()((patch_img[0] * 0.5 + 0.5).clamp(0,1))
|
| 820 |
-
patch_pil.save(patch_path)
|
| 821 |
-
|
| 822 |
-
# create prompt to explain patch (CoZ)
|
| 823 |
-
prompt = self.create_prompt(vlm_model, vlm_processor, full_path, patch_path, prompt_type)
|
| 824 |
-
print('PROMPT: ', prompt)
|
| 825 |
-
prompt_emb, pooled_emb = self.model.encode_prompt([prompt], batch_size=B)
|
| 826 |
-
prompt_emb = prompt_emb.to(z_full.device, dtype=torch.float32)
|
| 827 |
-
pooled_emb = pooled_emb.to(z_full.device, dtype=torch.float32)
|
| 828 |
-
|
| 829 |
-
v = self.predict_vector(patch, timestep, prompt_emb, pooled_emb)
|
| 830 |
-
out_tiles.append(patch - v)
|
| 831 |
-
|
| 832 |
-
# 5) accumulate + normalize
|
| 833 |
-
z_out = torch.zeros_like(z_full)
|
| 834 |
-
z_norm = torch.zeros_like(z_full)
|
| 835 |
-
norm = torch.zeros_like(z_full)
|
| 836 |
-
|
| 837 |
-
for (y0,x0), tile in zip(positions, out_tiles):
|
| 838 |
-
z_out[:, :, y0:y0+tsize, x0:x0+tsize] += tile
|
| 839 |
-
z_norm[:, :, y0:y0+tsize, x0:x0+tsize] += tile * weights
|
| 840 |
-
norm[:, :, y0:y0+tsize, x0:x0+tsize] += weights
|
| 841 |
-
|
| 842 |
-
# 6) avoid division by zero and finalize
|
| 843 |
-
eps = 1e-10
|
| 844 |
-
z_norm = z_norm / (norm + eps)
|
| 845 |
-
return z_norm, z_out
|
| 846 |
-
|
| 847 |
-
@torch.no_grad()
|
| 848 |
-
def decode_full_latent(self, z_full: torch.Tensor) -> torch.Tensor:
|
| 849 |
-
"""
|
| 850 |
-
Decode the tiled full latent into an RGB image (with tiled VAE decoder).
|
| 851 |
-
"""
|
| 852 |
-
z_full = z_full.to(self.model.vae.device)
|
| 853 |
-
img = self.model.vae.decode(z_full / self.model.vae.config.scaling_factor).sample
|
| 854 |
-
return img.clamp(-1,1)
|
| 855 |
-
|
| 856 |
-
@torch.no_grad()
|
| 857 |
-
def create_prompt(self, vlm_model, vlm_processor, full_path, patch_path, prompt_type):
|
| 858 |
-
if prompt_type in ('vlm','vlm_base'):
|
| 859 |
-
from qwen_vl_utils import process_vision_info
|
| 860 |
-
|
| 861 |
-
message_text = None
|
| 862 |
-
start_image_path = full_path
|
| 863 |
-
input_image_path = patch_path
|
| 864 |
-
|
| 865 |
-
message_text = "The second image is a zoom-in of the first image. Based on this knowledge, what is in the second image? Give me a set of words."
|
| 866 |
-
messages = [
|
| 867 |
-
{"role": "system", "content": f"{message_text}"},
|
| 868 |
-
{
|
| 869 |
-
"role": "user",
|
| 870 |
-
"content": [
|
| 871 |
-
{"type": "image", "image": start_image_path},
|
| 872 |
-
{"type": "image", "image": input_image_path}
|
| 873 |
-
]
|
| 874 |
-
}
|
| 875 |
-
]
|
| 876 |
-
|
| 877 |
-
text = vlm_processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
| 878 |
-
image_inputs, video_inputs = process_vision_info(messages)
|
| 879 |
-
inputs = vlm_processor(
|
| 880 |
-
text=[text],
|
| 881 |
-
images=image_inputs,
|
| 882 |
-
videos=video_inputs,
|
| 883 |
-
padding=True,
|
| 884 |
-
return_tensors="pt",
|
| 885 |
-
)
|
| 886 |
-
generated_ids = vlm_model.generate(**inputs, max_new_tokens=16)
|
| 887 |
-
generated_ids_trimmed = [
|
| 888 |
-
out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
|
| 889 |
-
]
|
| 890 |
-
output_text = vlm_processor.batch_decode(
|
| 891 |
-
generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
|
| 892 |
-
)
|
| 893 |
-
|
| 894 |
-
prompt_text = output_text[0]
|
| 895 |
-
return prompt_text
|
| 896 |
-
else:
|
| 897 |
-
raise ValueError(f"Unknown prompt_type: {prompt_type}")
|
| 898 |
-
|
| 899 |
-
def _make_gaussian(self, w, h, nb):
|
| 900 |
-
from numpy import pi, exp, sqrt
|
| 901 |
-
import numpy as np
|
| 902 |
-
|
| 903 |
-
latent_width = w
|
| 904 |
-
latent_height = h
|
| 905 |
-
|
| 906 |
-
var = 0.01
|
| 907 |
-
midpoint = (latent_width - 1) / 2 # -1 because index goes from 0 to latent_width - 1
|
| 908 |
-
x_probs = [exp(-(x-midpoint)*(x-midpoint)/(latent_width*latent_width)/(2*var)) / sqrt(2*pi*var) for x in range(latent_width)]
|
| 909 |
-
midpoint = latent_height / 2
|
| 910 |
-
y_probs = [exp(-(y-midpoint)*(y-midpoint)/(latent_height*latent_height)/(2*var)) / sqrt(2*pi*var) for y in range(latent_height)]
|
| 911 |
-
|
| 912 |
-
weights = np.outer(y_probs, x_probs)
|
| 913 |
-
return torch.tile(torch.tensor(weights, device=self.model.vae.device), (nb, self.model.vae.config.latent_channels, 1, 1))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vendor/utils/devices.py
DELETED
|
@@ -1,138 +0,0 @@
|
|
| 1 |
-
import sys
|
| 2 |
-
import contextlib
|
| 3 |
-
from functools import lru_cache
|
| 4 |
-
|
| 5 |
-
import torch
|
| 6 |
-
#from modules import errors
|
| 7 |
-
|
| 8 |
-
if sys.platform == "darwin":
|
| 9 |
-
from modules import mac_specific
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
def has_mps() -> bool:
|
| 13 |
-
if sys.platform != "darwin":
|
| 14 |
-
return False
|
| 15 |
-
else:
|
| 16 |
-
return mac_specific.has_mps
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
def get_cuda_device_string():
|
| 20 |
-
return "cuda"
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def get_optimal_device_name():
|
| 24 |
-
if torch.cuda.is_available():
|
| 25 |
-
return get_cuda_device_string()
|
| 26 |
-
|
| 27 |
-
if has_mps():
|
| 28 |
-
return "mps"
|
| 29 |
-
|
| 30 |
-
return "cpu"
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
def get_optimal_device():
|
| 34 |
-
return torch.device(get_optimal_device_name())
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
def get_device_for(task):
|
| 38 |
-
return get_optimal_device()
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
def torch_gc():
|
| 42 |
-
|
| 43 |
-
if torch.cuda.is_available():
|
| 44 |
-
with torch.cuda.device(get_cuda_device_string()):
|
| 45 |
-
torch.cuda.empty_cache()
|
| 46 |
-
torch.cuda.ipc_collect()
|
| 47 |
-
|
| 48 |
-
if has_mps():
|
| 49 |
-
mac_specific.torch_mps_gc()
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
def enable_tf32():
|
| 53 |
-
if torch.cuda.is_available():
|
| 54 |
-
|
| 55 |
-
# enabling benchmark option seems to enable a range of cards to do fp16 when they otherwise can't
|
| 56 |
-
# see https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/4407
|
| 57 |
-
if any(torch.cuda.get_device_capability(devid) == (7, 5) for devid in range(0, torch.cuda.device_count())):
|
| 58 |
-
torch.backends.cudnn.benchmark = True
|
| 59 |
-
|
| 60 |
-
torch.backends.cuda.matmul.allow_tf32 = True
|
| 61 |
-
torch.backends.cudnn.allow_tf32 = True
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
enable_tf32()
|
| 65 |
-
#errors.run(enable_tf32, "Enabling TF32")
|
| 66 |
-
|
| 67 |
-
cpu = torch.device("cpu")
|
| 68 |
-
device = device_interrogate = device_gfpgan = device_esrgan = device_codeformer = torch.device("cuda")
|
| 69 |
-
dtype = torch.float16
|
| 70 |
-
dtype_vae = torch.float16
|
| 71 |
-
dtype_unet = torch.float16
|
| 72 |
-
unet_needs_upcast = False
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
def cond_cast_unet(input):
|
| 76 |
-
return input.to(dtype_unet) if unet_needs_upcast else input
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
def cond_cast_float(input):
|
| 80 |
-
return input.float() if unet_needs_upcast else input
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
def randn(seed, shape):
|
| 84 |
-
torch.manual_seed(seed)
|
| 85 |
-
return torch.randn(shape, device=device)
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
def randn_without_seed(shape):
|
| 89 |
-
return torch.randn(shape, device=device)
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
def autocast(disable=False):
|
| 93 |
-
if disable:
|
| 94 |
-
return contextlib.nullcontext()
|
| 95 |
-
|
| 96 |
-
return torch.autocast("cuda")
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
def without_autocast(disable=False):
|
| 100 |
-
return torch.autocast("cuda", enabled=False) if torch.is_autocast_enabled() and not disable else contextlib.nullcontext()
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
class NansException(Exception):
|
| 104 |
-
pass
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
def test_for_nans(x, where):
|
| 108 |
-
if not torch.all(torch.isnan(x)).item():
|
| 109 |
-
return
|
| 110 |
-
|
| 111 |
-
if where == "unet":
|
| 112 |
-
message = "A tensor with all NaNs was produced in Unet."
|
| 113 |
-
|
| 114 |
-
elif where == "vae":
|
| 115 |
-
message = "A tensor with all NaNs was produced in VAE."
|
| 116 |
-
|
| 117 |
-
else:
|
| 118 |
-
message = "A tensor with all NaNs was produced."
|
| 119 |
-
|
| 120 |
-
message += " Use --disable-nan-check commandline argument to disable this check."
|
| 121 |
-
|
| 122 |
-
raise NansException(message)
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
@lru_cache
|
| 126 |
-
def first_time_calculation():
|
| 127 |
-
"""
|
| 128 |
-
just do any calculation with pytorch layers - the first time this is done it allocaltes about 700MB of memory and
|
| 129 |
-
spends about 2.7 seconds doing that, at least wih NVidia.
|
| 130 |
-
"""
|
| 131 |
-
|
| 132 |
-
x = torch.zeros((1, 1)).to(device, dtype)
|
| 133 |
-
linear = torch.nn.Linear(1, 1).to(device, dtype)
|
| 134 |
-
linear(x)
|
| 135 |
-
|
| 136 |
-
x = torch.zeros((1, 1, 3, 3)).to(device, dtype)
|
| 137 |
-
conv2d = torch.nn.Conv2d(1, 1, (3, 3)).to(device, dtype)
|
| 138 |
-
conv2d(x)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vendor/utils/vaehook.py
DELETED
|
@@ -1,829 +0,0 @@
|
|
| 1 |
-
# ------------------------------------------------------------------------
|
| 2 |
-
#
|
| 3 |
-
# Ultimate VAE Tile Optimization
|
| 4 |
-
#
|
| 5 |
-
# Introducing a revolutionary new optimization designed to make
|
| 6 |
-
# the VAE work with giant images on limited VRAM!
|
| 7 |
-
# Say goodbye to the frustration of OOM and hello to seamless output!
|
| 8 |
-
#
|
| 9 |
-
# ------------------------------------------------------------------------
|
| 10 |
-
#
|
| 11 |
-
# This script is a wild hack that splits the image into tiles,
|
| 12 |
-
# encodes each tile separately, and merges the result back together.
|
| 13 |
-
#
|
| 14 |
-
# Advantages:
|
| 15 |
-
# - The VAE can now work with giant images on limited VRAM
|
| 16 |
-
# (~10 GB for 8K images!)
|
| 17 |
-
# - The merged output is completely seamless without any post-processing.
|
| 18 |
-
#
|
| 19 |
-
# Drawbacks:
|
| 20 |
-
# - Giant RAM needed. To store the intermediate results for a 4096x4096
|
| 21 |
-
# images, you need 32 GB RAM it consumes ~20GB); for 8192x8192
|
| 22 |
-
# you need 128 GB RAM machine (it consumes ~100 GB)
|
| 23 |
-
# - NaNs always appear in for 8k images when you use fp16 (half) VAE
|
| 24 |
-
# You must use --no-half-vae to disable half VAE for that giant image.
|
| 25 |
-
# - Slow speed. With default tile size, it takes around 50/200 seconds
|
| 26 |
-
# to encode/decode a 4096x4096 image; and 200/900 seconds to encode/decode
|
| 27 |
-
# a 8192x8192 image. (The speed is limited by both the GPU and the CPU.)
|
| 28 |
-
# - The gradient calculation is not compatible with this hack. It
|
| 29 |
-
# will break any backward() or torch.autograd.grad() that passes VAE.
|
| 30 |
-
# (But you can still use the VAE to generate training data.)
|
| 31 |
-
#
|
| 32 |
-
# How it works:
|
| 33 |
-
# 1) The image is split into tiles.
|
| 34 |
-
# - To ensure perfect results, each tile is padded with 32 pixels
|
| 35 |
-
# on each side.
|
| 36 |
-
# - Then the conv2d/silu/upsample/downsample can produce identical
|
| 37 |
-
# results to the original image without splitting.
|
| 38 |
-
# 2) The original forward is decomposed into a task queue and a task worker.
|
| 39 |
-
# - The task queue is a list of functions that will be executed in order.
|
| 40 |
-
# - The task worker is a loop that executes the tasks in the queue.
|
| 41 |
-
# 3) The task queue is executed for each tile.
|
| 42 |
-
# - Current tile is sent to GPU.
|
| 43 |
-
# - local operations are directly executed.
|
| 44 |
-
# - Group norm calculation is temporarily suspended until the mean
|
| 45 |
-
# and var of all tiles are calculated.
|
| 46 |
-
# - The residual is pre-calculated and stored and addded back later.
|
| 47 |
-
# - When need to go to the next tile, the current tile is send to cpu.
|
| 48 |
-
# 4) After all tiles are processed, tiles are merged on cpu and return.
|
| 49 |
-
#
|
| 50 |
-
# Enjoy!
|
| 51 |
-
#
|
| 52 |
-
# @author: LI YI @ Nanyang Technological University - Singapore
|
| 53 |
-
# @date: 2023-03-02
|
| 54 |
-
# @license: MIT License
|
| 55 |
-
#
|
| 56 |
-
# Please give me a star if you like this project!
|
| 57 |
-
#
|
| 58 |
-
# -------------------------------------------------------------------------
|
| 59 |
-
|
| 60 |
-
import gc
|
| 61 |
-
from time import time
|
| 62 |
-
import math
|
| 63 |
-
from tqdm import tqdm
|
| 64 |
-
|
| 65 |
-
import torch
|
| 66 |
-
import torch.version
|
| 67 |
-
import torch.nn.functional as F
|
| 68 |
-
from einops import rearrange
|
| 69 |
-
import os
|
| 70 |
-
import sys
|
| 71 |
-
sys.path.append(os.getcwd())
|
| 72 |
-
import utils.devices as devices
|
| 73 |
-
|
| 74 |
-
try:
|
| 75 |
-
import xformers
|
| 76 |
-
import xformers.ops
|
| 77 |
-
except ImportError:
|
| 78 |
-
pass
|
| 79 |
-
|
| 80 |
-
sd_flag = False
|
| 81 |
-
|
| 82 |
-
def get_recommend_encoder_tile_size():
|
| 83 |
-
if torch.cuda.is_available():
|
| 84 |
-
total_memory = torch.cuda.get_device_properties(
|
| 85 |
-
devices.device).total_memory // 2**20
|
| 86 |
-
if total_memory > 16*1000:
|
| 87 |
-
ENCODER_TILE_SIZE = 3072
|
| 88 |
-
elif total_memory > 12*1000:
|
| 89 |
-
ENCODER_TILE_SIZE = 2048
|
| 90 |
-
elif total_memory > 8*1000:
|
| 91 |
-
ENCODER_TILE_SIZE = 1536
|
| 92 |
-
else:
|
| 93 |
-
ENCODER_TILE_SIZE = 960
|
| 94 |
-
else:
|
| 95 |
-
ENCODER_TILE_SIZE = 512
|
| 96 |
-
return ENCODER_TILE_SIZE
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
def get_recommend_decoder_tile_size():
|
| 100 |
-
if torch.cuda.is_available():
|
| 101 |
-
total_memory = torch.cuda.get_device_properties(
|
| 102 |
-
devices.device).total_memory // 2**20
|
| 103 |
-
if total_memory > 30*1000:
|
| 104 |
-
DECODER_TILE_SIZE = 256
|
| 105 |
-
elif total_memory > 16*1000:
|
| 106 |
-
DECODER_TILE_SIZE = 192
|
| 107 |
-
elif total_memory > 12*1000:
|
| 108 |
-
DECODER_TILE_SIZE = 128
|
| 109 |
-
elif total_memory > 8*1000:
|
| 110 |
-
DECODER_TILE_SIZE = 96
|
| 111 |
-
else:
|
| 112 |
-
DECODER_TILE_SIZE = 64
|
| 113 |
-
else:
|
| 114 |
-
DECODER_TILE_SIZE = 64
|
| 115 |
-
return DECODER_TILE_SIZE
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
if 'global const':
|
| 119 |
-
DEFAULT_ENABLED = False
|
| 120 |
-
DEFAULT_MOVE_TO_GPU = False
|
| 121 |
-
DEFAULT_FAST_ENCODER = True
|
| 122 |
-
DEFAULT_FAST_DECODER = True
|
| 123 |
-
DEFAULT_COLOR_FIX = 0
|
| 124 |
-
DEFAULT_ENCODER_TILE_SIZE = get_recommend_encoder_tile_size()
|
| 125 |
-
DEFAULT_DECODER_TILE_SIZE = get_recommend_decoder_tile_size()
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
# inplace version of silu
|
| 129 |
-
def inplace_nonlinearity(x):
|
| 130 |
-
# Test: fix for Nans
|
| 131 |
-
return F.silu(x, inplace=True)
|
| 132 |
-
|
| 133 |
-
# extracted from ldm.modules.diffusionmodules.model
|
| 134 |
-
|
| 135 |
-
# from diffusers lib
|
| 136 |
-
def attn_forward_new(self, h_):
|
| 137 |
-
batch_size, channel, height, width = h_.shape
|
| 138 |
-
hidden_states = h_.view(batch_size, channel, height * width).transpose(1, 2)
|
| 139 |
-
|
| 140 |
-
attention_mask = None
|
| 141 |
-
encoder_hidden_states = None
|
| 142 |
-
batch_size, sequence_length, _ = hidden_states.shape
|
| 143 |
-
attention_mask = self.prepare_attention_mask(attention_mask, sequence_length, batch_size)
|
| 144 |
-
|
| 145 |
-
query = self.to_q(hidden_states)
|
| 146 |
-
|
| 147 |
-
if encoder_hidden_states is None:
|
| 148 |
-
encoder_hidden_states = hidden_states
|
| 149 |
-
elif self.norm_cross:
|
| 150 |
-
encoder_hidden_states = self.norm_encoder_hidden_states(encoder_hidden_states)
|
| 151 |
-
|
| 152 |
-
key = self.to_k(encoder_hidden_states)
|
| 153 |
-
value = self.to_v(encoder_hidden_states)
|
| 154 |
-
|
| 155 |
-
query = self.head_to_batch_dim(query)
|
| 156 |
-
key = self.head_to_batch_dim(key)
|
| 157 |
-
value = self.head_to_batch_dim(value)
|
| 158 |
-
|
| 159 |
-
attention_probs = self.get_attention_scores(query, key, attention_mask)
|
| 160 |
-
hidden_states = torch.bmm(attention_probs, value)
|
| 161 |
-
hidden_states = self.batch_to_head_dim(hidden_states)
|
| 162 |
-
|
| 163 |
-
# linear proj
|
| 164 |
-
hidden_states = self.to_out[0](hidden_states)
|
| 165 |
-
# dropout
|
| 166 |
-
hidden_states = self.to_out[1](hidden_states)
|
| 167 |
-
|
| 168 |
-
hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width)
|
| 169 |
-
|
| 170 |
-
return hidden_states
|
| 171 |
-
|
| 172 |
-
def attn_forward(self, h_):
|
| 173 |
-
q = self.q(h_)
|
| 174 |
-
k = self.k(h_)
|
| 175 |
-
v = self.v(h_)
|
| 176 |
-
|
| 177 |
-
# compute attention
|
| 178 |
-
b, c, h, w = q.shape
|
| 179 |
-
q = q.reshape(b, c, h*w)
|
| 180 |
-
q = q.permute(0, 2, 1) # b,hw,c
|
| 181 |
-
k = k.reshape(b, c, h*w) # b,c,hw
|
| 182 |
-
w_ = torch.bmm(q, k) # b,hw,hw w[b,i,j]=sum_c q[b,i,c]k[b,c,j]
|
| 183 |
-
w_ = w_ * (int(c)**(-0.5))
|
| 184 |
-
w_ = torch.nn.functional.softmax(w_, dim=2)
|
| 185 |
-
|
| 186 |
-
# attend to values
|
| 187 |
-
v = v.reshape(b, c, h*w)
|
| 188 |
-
w_ = w_.permute(0, 2, 1) # b,hw,hw (first hw of k, second of q)
|
| 189 |
-
# b, c,hw (hw of q) h_[b,c,j] = sum_i v[b,c,i] w_[b,i,j]
|
| 190 |
-
h_ = torch.bmm(v, w_)
|
| 191 |
-
h_ = h_.reshape(b, c, h, w)
|
| 192 |
-
|
| 193 |
-
h_ = self.proj_out(h_)
|
| 194 |
-
|
| 195 |
-
return h_
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
def xformer_attn_forward(self, h_):
|
| 199 |
-
q = self.q(h_)
|
| 200 |
-
k = self.k(h_)
|
| 201 |
-
v = self.v(h_)
|
| 202 |
-
|
| 203 |
-
# compute attention
|
| 204 |
-
B, C, H, W = q.shape
|
| 205 |
-
q, k, v = map(lambda x: rearrange(x, 'b c h w -> b (h w) c'), (q, k, v))
|
| 206 |
-
|
| 207 |
-
q, k, v = map(
|
| 208 |
-
lambda t: t.unsqueeze(3)
|
| 209 |
-
.reshape(B, t.shape[1], 1, C)
|
| 210 |
-
.permute(0, 2, 1, 3)
|
| 211 |
-
.reshape(B * 1, t.shape[1], C)
|
| 212 |
-
.contiguous(),
|
| 213 |
-
(q, k, v),
|
| 214 |
-
)
|
| 215 |
-
out = xformers.ops.memory_efficient_attention(
|
| 216 |
-
q, k, v, attn_bias=None, op=self.attention_op)
|
| 217 |
-
|
| 218 |
-
out = (
|
| 219 |
-
out.unsqueeze(0)
|
| 220 |
-
.reshape(B, 1, out.shape[1], C)
|
| 221 |
-
.permute(0, 2, 1, 3)
|
| 222 |
-
.reshape(B, out.shape[1], C)
|
| 223 |
-
)
|
| 224 |
-
out = rearrange(out, 'b (h w) c -> b c h w', b=B, h=H, w=W, c=C)
|
| 225 |
-
out = self.proj_out(out)
|
| 226 |
-
return out
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
def attn2task(task_queue, net):
|
| 230 |
-
if False: #isinstance(net, AttnBlock):
|
| 231 |
-
task_queue.append(('store_res', lambda x: x))
|
| 232 |
-
task_queue.append(('pre_norm', net.norm))
|
| 233 |
-
task_queue.append(('attn', lambda x, net=net: attn_forward(net, x)))
|
| 234 |
-
task_queue.append(['add_res', None])
|
| 235 |
-
elif False: #isinstance(net, MemoryEfficientAttnBlock):
|
| 236 |
-
task_queue.append(('store_res', lambda x: x))
|
| 237 |
-
task_queue.append(('pre_norm', net.norm))
|
| 238 |
-
task_queue.append(
|
| 239 |
-
('attn', lambda x, net=net: xformer_attn_forward(net, x)))
|
| 240 |
-
task_queue.append(['add_res', None])
|
| 241 |
-
else:
|
| 242 |
-
task_queue.append(('store_res', lambda x: x))
|
| 243 |
-
task_queue.append(('pre_norm', net.group_norm))
|
| 244 |
-
task_queue.append(('attn', lambda x, net=net: attn_forward_new(net, x)))
|
| 245 |
-
task_queue.append(['add_res', None])
|
| 246 |
-
|
| 247 |
-
def resblock2task(queue, block):
|
| 248 |
-
"""
|
| 249 |
-
Turn a ResNetBlock into a sequence of tasks and append to the task queue
|
| 250 |
-
|
| 251 |
-
@param queue: the target task queue
|
| 252 |
-
@param block: ResNetBlock
|
| 253 |
-
|
| 254 |
-
"""
|
| 255 |
-
if block.in_channels != block.out_channels:
|
| 256 |
-
if sd_flag:
|
| 257 |
-
if block.use_conv_shortcut:
|
| 258 |
-
queue.append(('store_res', block.conv_shortcut))
|
| 259 |
-
else:
|
| 260 |
-
queue.append(('store_res', block.nin_shortcut))
|
| 261 |
-
else:
|
| 262 |
-
if block.use_in_shortcut:
|
| 263 |
-
queue.append(('store_res', block.conv_shortcut))
|
| 264 |
-
else:
|
| 265 |
-
queue.append(('store_res', block.nin_shortcut))
|
| 266 |
-
|
| 267 |
-
else:
|
| 268 |
-
queue.append(('store_res', lambda x: x))
|
| 269 |
-
queue.append(('pre_norm', block.norm1))
|
| 270 |
-
queue.append(('silu', inplace_nonlinearity))
|
| 271 |
-
queue.append(('conv1', block.conv1))
|
| 272 |
-
queue.append(('pre_norm', block.norm2))
|
| 273 |
-
queue.append(('silu', inplace_nonlinearity))
|
| 274 |
-
queue.append(('conv2', block.conv2))
|
| 275 |
-
queue.append(['add_res', None])
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
def build_sampling(task_queue, net, is_decoder):
|
| 280 |
-
"""
|
| 281 |
-
Build the sampling part of a task queue
|
| 282 |
-
@param task_queue: the target task queue
|
| 283 |
-
@param net: the network
|
| 284 |
-
@param is_decoder: currently building decoder or encoder
|
| 285 |
-
"""
|
| 286 |
-
if is_decoder:
|
| 287 |
-
# resblock2task(task_queue, net.mid.block_1)
|
| 288 |
-
# attn2task(task_queue, net.mid.attn_1)
|
| 289 |
-
# resblock2task(task_queue, net.mid.block_2)
|
| 290 |
-
# resolution_iter = reversed(range(net.num_resolutions))
|
| 291 |
-
# block_ids = net.num_res_blocks + 1
|
| 292 |
-
# condition = 0
|
| 293 |
-
# module = net.up
|
| 294 |
-
# func_name = 'upsample'
|
| 295 |
-
resblock2task(task_queue, net.mid_block.resnets[0])
|
| 296 |
-
attn2task(task_queue, net.mid_block.attentions[0])
|
| 297 |
-
resblock2task(task_queue, net.mid_block.resnets[1])
|
| 298 |
-
resolution_iter = (range(len(net.up_blocks))) # range(0,4)
|
| 299 |
-
block_ids = 2 + 1
|
| 300 |
-
condition = len(net.up_blocks) - 1
|
| 301 |
-
module = net.up_blocks
|
| 302 |
-
func_name = 'upsamplers'
|
| 303 |
-
else:
|
| 304 |
-
# resolution_iter = range(net.num_resolutions)
|
| 305 |
-
# block_ids = net.num_res_blocks
|
| 306 |
-
# condition = net.num_resolutions - 1
|
| 307 |
-
# module = net.down
|
| 308 |
-
# func_name = 'downsample'
|
| 309 |
-
resolution_iter = (range(len(net.down_blocks))) # range(0,4)
|
| 310 |
-
block_ids = 2
|
| 311 |
-
condition = len(net.down_blocks) - 1
|
| 312 |
-
module = net.down_blocks
|
| 313 |
-
func_name = 'downsamplers'
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
for i_level in resolution_iter:
|
| 317 |
-
for i_block in range(block_ids):
|
| 318 |
-
resblock2task(task_queue, module[i_level].resnets[i_block])
|
| 319 |
-
if i_level != condition:
|
| 320 |
-
if is_decoder:
|
| 321 |
-
task_queue.append((func_name, module[i_level].upsamplers[0]))
|
| 322 |
-
else:
|
| 323 |
-
task_queue.append((func_name, module[i_level].downsamplers[0]))
|
| 324 |
-
|
| 325 |
-
if not is_decoder:
|
| 326 |
-
resblock2task(task_queue, net.mid_block.resnets[0])
|
| 327 |
-
attn2task(task_queue, net.mid_block.attentions[0])
|
| 328 |
-
resblock2task(task_queue, net.mid_block.resnets[1])
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
def build_task_queue(net, is_decoder):
|
| 332 |
-
"""
|
| 333 |
-
Build a single task queue for the encoder or decoder
|
| 334 |
-
@param net: the VAE decoder or encoder network
|
| 335 |
-
@param is_decoder: currently building decoder or encoder
|
| 336 |
-
@return: the task queue
|
| 337 |
-
"""
|
| 338 |
-
task_queue = []
|
| 339 |
-
task_queue.append(('conv_in', net.conv_in))
|
| 340 |
-
|
| 341 |
-
# construct the sampling part of the task queue
|
| 342 |
-
# because encoder and decoder share the same architecture, we extract the sampling part
|
| 343 |
-
build_sampling(task_queue, net, is_decoder)
|
| 344 |
-
if is_decoder and not sd_flag:
|
| 345 |
-
net.give_pre_end = False
|
| 346 |
-
net.tanh_out = False
|
| 347 |
-
|
| 348 |
-
if not is_decoder or not net.give_pre_end:
|
| 349 |
-
if sd_flag:
|
| 350 |
-
task_queue.append(('pre_norm', net.norm_out))
|
| 351 |
-
else:
|
| 352 |
-
task_queue.append(('pre_norm', net.conv_norm_out))
|
| 353 |
-
task_queue.append(('silu', inplace_nonlinearity))
|
| 354 |
-
task_queue.append(('conv_out', net.conv_out))
|
| 355 |
-
if is_decoder and net.tanh_out:
|
| 356 |
-
task_queue.append(('tanh', torch.tanh))
|
| 357 |
-
|
| 358 |
-
return task_queue
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
def clone_task_queue(task_queue):
|
| 362 |
-
"""
|
| 363 |
-
Clone a task queue
|
| 364 |
-
@param task_queue: the task queue to be cloned
|
| 365 |
-
@return: the cloned task queue
|
| 366 |
-
"""
|
| 367 |
-
return [[item for item in task] for task in task_queue]
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
def get_var_mean(input, num_groups, eps=1e-6):
|
| 371 |
-
"""
|
| 372 |
-
Get mean and var for group norm
|
| 373 |
-
"""
|
| 374 |
-
b, c = input.size(0), input.size(1)
|
| 375 |
-
channel_in_group = int(c/num_groups)
|
| 376 |
-
input_reshaped = input.contiguous().view(
|
| 377 |
-
1, int(b * num_groups), channel_in_group, *input.size()[2:])
|
| 378 |
-
var, mean = torch.var_mean(
|
| 379 |
-
input_reshaped, dim=[0, 2, 3, 4], unbiased=False)
|
| 380 |
-
return var, mean
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
def custom_group_norm(input, num_groups, mean, var, weight=None, bias=None, eps=1e-6):
|
| 384 |
-
"""
|
| 385 |
-
Custom group norm with fixed mean and var
|
| 386 |
-
|
| 387 |
-
@param input: input tensor
|
| 388 |
-
@param num_groups: number of groups. by default, num_groups = 32
|
| 389 |
-
@param mean: mean, must be pre-calculated by get_var_mean
|
| 390 |
-
@param var: var, must be pre-calculated by get_var_mean
|
| 391 |
-
@param weight: weight, should be fetched from the original group norm
|
| 392 |
-
@param bias: bias, should be fetched from the original group norm
|
| 393 |
-
@param eps: epsilon, by default, eps = 1e-6 to match the original group norm
|
| 394 |
-
|
| 395 |
-
@return: normalized tensor
|
| 396 |
-
"""
|
| 397 |
-
b, c = input.size(0), input.size(1)
|
| 398 |
-
channel_in_group = int(c/num_groups)
|
| 399 |
-
input_reshaped = input.contiguous().view(
|
| 400 |
-
1, int(b * num_groups), channel_in_group, *input.size()[2:])
|
| 401 |
-
|
| 402 |
-
out = F.batch_norm(input_reshaped, mean, var, weight=None, bias=None,
|
| 403 |
-
training=False, momentum=0, eps=eps)
|
| 404 |
-
|
| 405 |
-
out = out.view(b, c, *input.size()[2:])
|
| 406 |
-
|
| 407 |
-
# post affine transform
|
| 408 |
-
if weight is not None:
|
| 409 |
-
out *= weight.view(1, -1, 1, 1)
|
| 410 |
-
if bias is not None:
|
| 411 |
-
out += bias.view(1, -1, 1, 1)
|
| 412 |
-
return out
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
def crop_valid_region(x, input_bbox, target_bbox, is_decoder):
|
| 416 |
-
"""
|
| 417 |
-
Crop the valid region from the tile
|
| 418 |
-
@param x: input tile
|
| 419 |
-
@param input_bbox: original input bounding box
|
| 420 |
-
@param target_bbox: output bounding box
|
| 421 |
-
@param scale: scale factor
|
| 422 |
-
@return: cropped tile
|
| 423 |
-
"""
|
| 424 |
-
padded_bbox = [i * 8 if is_decoder else i//8 for i in input_bbox]
|
| 425 |
-
margin = [target_bbox[i] - padded_bbox[i] for i in range(4)]
|
| 426 |
-
return x[:, :, margin[2]:x.size(2)+margin[3], margin[0]:x.size(3)+margin[1]]
|
| 427 |
-
|
| 428 |
-
# ↓↓↓ https://github.com/Kahsolt/stable-diffusion-webui-vae-tile-infer ↓↓↓
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
def perfcount(fn):
|
| 432 |
-
def wrapper(*args, **kwargs):
|
| 433 |
-
ts = time()
|
| 434 |
-
|
| 435 |
-
if torch.cuda.is_available():
|
| 436 |
-
torch.cuda.reset_peak_memory_stats(devices.device)
|
| 437 |
-
devices.torch_gc()
|
| 438 |
-
gc.collect()
|
| 439 |
-
|
| 440 |
-
ret = fn(*args, **kwargs)
|
| 441 |
-
|
| 442 |
-
devices.torch_gc()
|
| 443 |
-
gc.collect()
|
| 444 |
-
if torch.cuda.is_available():
|
| 445 |
-
vram = torch.cuda.max_memory_allocated(devices.device) / 2**20
|
| 446 |
-
torch.cuda.reset_peak_memory_stats(devices.device)
|
| 447 |
-
print(
|
| 448 |
-
f'[Tiled VAE]: Done in {time() - ts:.3f}s, max VRAM alloc {vram:.3f} MB')
|
| 449 |
-
else:
|
| 450 |
-
print(f'[Tiled VAE]: Done in {time() - ts:.3f}s')
|
| 451 |
-
|
| 452 |
-
return ret
|
| 453 |
-
return wrapper
|
| 454 |
-
|
| 455 |
-
# copy end :)
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
class GroupNormParam:
|
| 459 |
-
def __init__(self):
|
| 460 |
-
self.var_list = []
|
| 461 |
-
self.mean_list = []
|
| 462 |
-
self.pixel_list = []
|
| 463 |
-
self.weight = None
|
| 464 |
-
self.bias = None
|
| 465 |
-
|
| 466 |
-
def add_tile(self, tile, layer):
|
| 467 |
-
var, mean = get_var_mean(tile, 32)
|
| 468 |
-
# For giant images, the variance can be larger than max float16
|
| 469 |
-
# In this case we create a copy to float32
|
| 470 |
-
if var.dtype == torch.float16 and var.isinf().any():
|
| 471 |
-
fp32_tile = tile.float()
|
| 472 |
-
var, mean = get_var_mean(fp32_tile, 32)
|
| 473 |
-
# ============= DEBUG: test for infinite =============
|
| 474 |
-
# if torch.isinf(var).any():
|
| 475 |
-
# print('var: ', var)
|
| 476 |
-
# ====================================================
|
| 477 |
-
self.var_list.append(var)
|
| 478 |
-
self.mean_list.append(mean)
|
| 479 |
-
self.pixel_list.append(
|
| 480 |
-
tile.shape[2]*tile.shape[3])
|
| 481 |
-
if hasattr(layer, 'weight'):
|
| 482 |
-
self.weight = layer.weight
|
| 483 |
-
self.bias = layer.bias
|
| 484 |
-
else:
|
| 485 |
-
self.weight = None
|
| 486 |
-
self.bias = None
|
| 487 |
-
|
| 488 |
-
def summary(self):
|
| 489 |
-
"""
|
| 490 |
-
summarize the mean and var and return a function
|
| 491 |
-
that apply group norm on each tile
|
| 492 |
-
"""
|
| 493 |
-
if len(self.var_list) == 0:
|
| 494 |
-
return None
|
| 495 |
-
var = torch.vstack(self.var_list)
|
| 496 |
-
mean = torch.vstack(self.mean_list)
|
| 497 |
-
max_value = max(self.pixel_list)
|
| 498 |
-
pixels = torch.tensor(
|
| 499 |
-
self.pixel_list, dtype=torch.float32, device=devices.device) / max_value
|
| 500 |
-
sum_pixels = torch.sum(pixels)
|
| 501 |
-
pixels = pixels.unsqueeze(
|
| 502 |
-
1) / sum_pixels
|
| 503 |
-
var = torch.sum(
|
| 504 |
-
var * pixels, dim=0)
|
| 505 |
-
mean = torch.sum(
|
| 506 |
-
mean * pixels, dim=0)
|
| 507 |
-
return lambda x: custom_group_norm(x, 32, mean, var, self.weight, self.bias)
|
| 508 |
-
|
| 509 |
-
@staticmethod
|
| 510 |
-
def from_tile(tile, norm):
|
| 511 |
-
"""
|
| 512 |
-
create a function from a single tile without summary
|
| 513 |
-
"""
|
| 514 |
-
var, mean = get_var_mean(tile, 32)
|
| 515 |
-
if var.dtype == torch.float16 and var.isinf().any():
|
| 516 |
-
fp32_tile = tile.float()
|
| 517 |
-
var, mean = get_var_mean(fp32_tile, 32)
|
| 518 |
-
# if it is a macbook, we need to convert back to float16
|
| 519 |
-
if var.device.type == 'mps':
|
| 520 |
-
# clamp to avoid overflow
|
| 521 |
-
var = torch.clamp(var, 0, 60000)
|
| 522 |
-
var = var.half()
|
| 523 |
-
mean = mean.half()
|
| 524 |
-
if hasattr(norm, 'weight'):
|
| 525 |
-
weight = norm.weight
|
| 526 |
-
bias = norm.bias
|
| 527 |
-
else:
|
| 528 |
-
weight = None
|
| 529 |
-
bias = None
|
| 530 |
-
|
| 531 |
-
def group_norm_func(x, mean=mean, var=var, weight=weight, bias=bias):
|
| 532 |
-
return custom_group_norm(x, 32, mean, var, weight, bias, 1e-6)
|
| 533 |
-
return group_norm_func
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
class VAEHook:
|
| 537 |
-
def __init__(self, net, tile_size, is_decoder, fast_decoder, fast_encoder, color_fix, to_gpu=False):
|
| 538 |
-
self.net = net # encoder | decoder
|
| 539 |
-
self.tile_size = tile_size
|
| 540 |
-
self.is_decoder = is_decoder
|
| 541 |
-
self.fast_mode = (fast_encoder and not is_decoder) or (
|
| 542 |
-
fast_decoder and is_decoder)
|
| 543 |
-
self.color_fix = color_fix and not is_decoder
|
| 544 |
-
self.to_gpu = to_gpu
|
| 545 |
-
self.pad = 11 if is_decoder else 32
|
| 546 |
-
|
| 547 |
-
def __call__(self, x):
|
| 548 |
-
B, C, H, W = x.shape
|
| 549 |
-
original_device = next(self.net.parameters()).device
|
| 550 |
-
try:
|
| 551 |
-
if self.to_gpu:
|
| 552 |
-
# self.net.to(devices.get_optimal_device())
|
| 553 |
-
self.net.to(original_device)
|
| 554 |
-
if max(H, W) <= self.pad * 2 + self.tile_size:
|
| 555 |
-
# print("[Tiled VAE]: the input size is tiny and unnecessary to tile.")
|
| 556 |
-
return self.net.original_forward(x).to(original_device)
|
| 557 |
-
else:
|
| 558 |
-
return self.vae_tile_forward(x)
|
| 559 |
-
finally:
|
| 560 |
-
self.net.to(original_device)
|
| 561 |
-
|
| 562 |
-
def get_best_tile_size(self, lowerbound, upperbound):
|
| 563 |
-
"""
|
| 564 |
-
Get the best tile size for GPU memory
|
| 565 |
-
"""
|
| 566 |
-
divider = 32
|
| 567 |
-
while divider >= 2:
|
| 568 |
-
remainer = lowerbound % divider
|
| 569 |
-
if remainer == 0:
|
| 570 |
-
return lowerbound
|
| 571 |
-
candidate = lowerbound - remainer + divider
|
| 572 |
-
if candidate <= upperbound:
|
| 573 |
-
return candidate
|
| 574 |
-
divider //= 2
|
| 575 |
-
return lowerbound
|
| 576 |
-
|
| 577 |
-
def split_tiles(self, h, w):
|
| 578 |
-
"""
|
| 579 |
-
Tool function to split the image into tiles
|
| 580 |
-
@param h: height of the image
|
| 581 |
-
@param w: width of the image
|
| 582 |
-
@return: tile_input_bboxes, tile_output_bboxes
|
| 583 |
-
"""
|
| 584 |
-
tile_input_bboxes, tile_output_bboxes = [], []
|
| 585 |
-
tile_size = self.tile_size
|
| 586 |
-
pad = self.pad
|
| 587 |
-
num_height_tiles = math.ceil((h - 2 * pad) / tile_size)
|
| 588 |
-
num_width_tiles = math.ceil((w - 2 * pad) / tile_size)
|
| 589 |
-
# If any of the numbers are 0, we let it be 1
|
| 590 |
-
# This is to deal with long and thin images
|
| 591 |
-
num_height_tiles = max(num_height_tiles, 1)
|
| 592 |
-
num_width_tiles = max(num_width_tiles, 1)
|
| 593 |
-
|
| 594 |
-
# Suggestions from https://github.com/Kahsolt: auto shrink the tile size
|
| 595 |
-
real_tile_height = math.ceil((h - 2 * pad) / num_height_tiles)
|
| 596 |
-
real_tile_width = math.ceil((w - 2 * pad) / num_width_tiles)
|
| 597 |
-
real_tile_height = self.get_best_tile_size(real_tile_height, tile_size)
|
| 598 |
-
real_tile_width = self.get_best_tile_size(real_tile_width, tile_size)
|
| 599 |
-
|
| 600 |
-
print(f'[Tiled VAE]: split to {num_height_tiles}x{num_width_tiles} = {num_height_tiles*num_width_tiles} tiles. ' +
|
| 601 |
-
f'Optimal tile size {real_tile_width}x{real_tile_height}, original tile size {tile_size}x{tile_size}')
|
| 602 |
-
|
| 603 |
-
for i in range(num_height_tiles):
|
| 604 |
-
for j in range(num_width_tiles):
|
| 605 |
-
# bbox: [x1, x2, y1, y2]
|
| 606 |
-
# the padding is is unnessary for image borders. So we directly start from (32, 32)
|
| 607 |
-
input_bbox = [
|
| 608 |
-
pad + j * real_tile_width,
|
| 609 |
-
min(pad + (j + 1) * real_tile_width, w),
|
| 610 |
-
pad + i * real_tile_height,
|
| 611 |
-
min(pad + (i + 1) * real_tile_height, h),
|
| 612 |
-
]
|
| 613 |
-
|
| 614 |
-
# if the output bbox is close to the image boundary, we extend it to the image boundary
|
| 615 |
-
output_bbox = [
|
| 616 |
-
input_bbox[0] if input_bbox[0] > pad else 0,
|
| 617 |
-
input_bbox[1] if input_bbox[1] < w - pad else w,
|
| 618 |
-
input_bbox[2] if input_bbox[2] > pad else 0,
|
| 619 |
-
input_bbox[3] if input_bbox[3] < h - pad else h,
|
| 620 |
-
]
|
| 621 |
-
|
| 622 |
-
# scale to get the final output bbox
|
| 623 |
-
output_bbox = [x * 8 if self.is_decoder else x // 8 for x in output_bbox]
|
| 624 |
-
tile_output_bboxes.append(output_bbox)
|
| 625 |
-
|
| 626 |
-
# indistinguishable expand the input bbox by pad pixels
|
| 627 |
-
tile_input_bboxes.append([
|
| 628 |
-
max(0, input_bbox[0] - pad),
|
| 629 |
-
min(w, input_bbox[1] + pad),
|
| 630 |
-
max(0, input_bbox[2] - pad),
|
| 631 |
-
min(h, input_bbox[3] + pad),
|
| 632 |
-
])
|
| 633 |
-
|
| 634 |
-
return tile_input_bboxes, tile_output_bboxes
|
| 635 |
-
|
| 636 |
-
@torch.no_grad()
|
| 637 |
-
def estimate_group_norm(self, z, task_queue, color_fix):
|
| 638 |
-
device = z.device
|
| 639 |
-
tile = z
|
| 640 |
-
last_id = len(task_queue) - 1
|
| 641 |
-
while last_id >= 0 and task_queue[last_id][0] != 'pre_norm':
|
| 642 |
-
last_id -= 1
|
| 643 |
-
if last_id <= 0 or task_queue[last_id][0] != 'pre_norm':
|
| 644 |
-
raise ValueError('No group norm found in the task queue')
|
| 645 |
-
# estimate until the last group norm
|
| 646 |
-
for i in range(last_id + 1):
|
| 647 |
-
task = task_queue[i]
|
| 648 |
-
if task[0] == 'pre_norm':
|
| 649 |
-
group_norm_func = GroupNormParam.from_tile(tile, task[1])
|
| 650 |
-
task_queue[i] = ('apply_norm', group_norm_func)
|
| 651 |
-
if i == last_id:
|
| 652 |
-
return True
|
| 653 |
-
tile = group_norm_func(tile)
|
| 654 |
-
elif task[0] == 'store_res':
|
| 655 |
-
task_id = i + 1
|
| 656 |
-
while task_id < last_id and task_queue[task_id][0] != 'add_res':
|
| 657 |
-
task_id += 1
|
| 658 |
-
if task_id >= last_id:
|
| 659 |
-
continue
|
| 660 |
-
task_queue[task_id][1] = task[1](tile)
|
| 661 |
-
elif task[0] == 'add_res':
|
| 662 |
-
tile += task[1].to(device)
|
| 663 |
-
task[1] = None
|
| 664 |
-
elif color_fix and task[0] == 'downsample':
|
| 665 |
-
for j in range(i, last_id + 1):
|
| 666 |
-
if task_queue[j][0] == 'store_res':
|
| 667 |
-
task_queue[j] = ('store_res_cpu', task_queue[j][1])
|
| 668 |
-
return True
|
| 669 |
-
else:
|
| 670 |
-
tile = task[1](tile)
|
| 671 |
-
try:
|
| 672 |
-
devices.test_for_nans(tile, "vae")
|
| 673 |
-
except:
|
| 674 |
-
print(f'Nan detected in fast mode estimation. Fast mode disabled.')
|
| 675 |
-
return False
|
| 676 |
-
|
| 677 |
-
raise IndexError('Should not reach here')
|
| 678 |
-
|
| 679 |
-
# @perfcount
|
| 680 |
-
@torch.no_grad()
|
| 681 |
-
def vae_tile_forward(self, z):
|
| 682 |
-
"""
|
| 683 |
-
Decode a latent vector z into an image in a tiled manner.
|
| 684 |
-
@param z: latent vector
|
| 685 |
-
@return: image
|
| 686 |
-
"""
|
| 687 |
-
device = next(self.net.parameters()).device
|
| 688 |
-
net = self.net
|
| 689 |
-
tile_size = self.tile_size
|
| 690 |
-
is_decoder = self.is_decoder
|
| 691 |
-
|
| 692 |
-
z = z.detach() # detach the input to avoid backprop
|
| 693 |
-
|
| 694 |
-
N, height, width = z.shape[0], z.shape[2], z.shape[3]
|
| 695 |
-
net.last_z_shape = z.shape
|
| 696 |
-
|
| 697 |
-
# Split the input into tiles and build a task queue for each tile
|
| 698 |
-
print(f'[Tiled VAE]: input_size: {z.shape}, tile_size: {tile_size}, padding: {self.pad}')
|
| 699 |
-
|
| 700 |
-
in_bboxes, out_bboxes = self.split_tiles(height, width)
|
| 701 |
-
|
| 702 |
-
# Prepare tiles by split the input latents
|
| 703 |
-
tiles = []
|
| 704 |
-
for input_bbox in in_bboxes:
|
| 705 |
-
tile = z[:, :, input_bbox[2]:input_bbox[3], input_bbox[0]:input_bbox[1]].cpu()
|
| 706 |
-
tiles.append(tile)
|
| 707 |
-
|
| 708 |
-
num_tiles = len(tiles)
|
| 709 |
-
num_completed = 0
|
| 710 |
-
|
| 711 |
-
# Build task queues
|
| 712 |
-
single_task_queue = build_task_queue(net, is_decoder)
|
| 713 |
-
#print(single_task_queue)
|
| 714 |
-
if self.fast_mode:
|
| 715 |
-
# Fast mode: downsample the input image to the tile size,
|
| 716 |
-
# then estimate the group norm parameters on the downsampled image
|
| 717 |
-
scale_factor = tile_size / max(height, width)
|
| 718 |
-
z = z.to(device)
|
| 719 |
-
downsampled_z = F.interpolate(z, scale_factor=scale_factor, mode='nearest-exact')
|
| 720 |
-
# use nearest-exact to keep statictics as close as possible
|
| 721 |
-
print(f'[Tiled VAE]: Fast mode enabled, estimating group norm parameters on {downsampled_z.shape[3]} x {downsampled_z.shape[2]} image')
|
| 722 |
-
|
| 723 |
-
# ======= Special thanks to @Kahsolt for distribution shift issue ======= #
|
| 724 |
-
# The downsampling will heavily distort its mean and std, so we need to recover it.
|
| 725 |
-
std_old, mean_old = torch.std_mean(z, dim=[0, 2, 3], keepdim=True)
|
| 726 |
-
std_new, mean_new = torch.std_mean(downsampled_z, dim=[0, 2, 3], keepdim=True)
|
| 727 |
-
downsampled_z = (downsampled_z - mean_new) / std_new * std_old + mean_old
|
| 728 |
-
del std_old, mean_old, std_new, mean_new
|
| 729 |
-
# occasionally the std_new is too small or too large, which exceeds the range of float16
|
| 730 |
-
# so we need to clamp it to max z's range.
|
| 731 |
-
downsampled_z = torch.clamp_(downsampled_z, min=z.min(), max=z.max())
|
| 732 |
-
estimate_task_queue = clone_task_queue(single_task_queue)
|
| 733 |
-
if self.estimate_group_norm(downsampled_z, estimate_task_queue, color_fix=self.color_fix):
|
| 734 |
-
single_task_queue = estimate_task_queue
|
| 735 |
-
del downsampled_z
|
| 736 |
-
|
| 737 |
-
task_queues = [clone_task_queue(single_task_queue) for _ in range(num_tiles)]
|
| 738 |
-
|
| 739 |
-
# Dummy result
|
| 740 |
-
result = None
|
| 741 |
-
result_approx = None
|
| 742 |
-
#try:
|
| 743 |
-
# with devices.autocast():
|
| 744 |
-
# result_approx = torch.cat([F.interpolate(cheap_approximation(x).unsqueeze(0), scale_factor=opt_f, mode='nearest-exact') for x in z], dim=0).cpu()
|
| 745 |
-
#except: pass
|
| 746 |
-
# Free memory of input latent tensor
|
| 747 |
-
del z
|
| 748 |
-
|
| 749 |
-
# Task queue execution
|
| 750 |
-
pbar = tqdm(total=num_tiles * len(task_queues[0]), desc=f"[Tiled VAE]: Executing {'Decoder' if is_decoder else 'Encoder'} Task Queue: ")
|
| 751 |
-
|
| 752 |
-
# execute the task back and forth when switch tiles so that we always
|
| 753 |
-
# keep one tile on the GPU to reduce unnecessary data transfer
|
| 754 |
-
forward = True
|
| 755 |
-
interrupted = False
|
| 756 |
-
#state.interrupted = interrupted
|
| 757 |
-
while True:
|
| 758 |
-
#if state.interrupted: interrupted = True ; break
|
| 759 |
-
|
| 760 |
-
group_norm_param = GroupNormParam()
|
| 761 |
-
for i in range(num_tiles) if forward else reversed(range(num_tiles)):
|
| 762 |
-
#if state.interrupted: interrupted = True ; break
|
| 763 |
-
|
| 764 |
-
tile = tiles[i].to(device)
|
| 765 |
-
input_bbox = in_bboxes[i]
|
| 766 |
-
task_queue = task_queues[i]
|
| 767 |
-
|
| 768 |
-
interrupted = False
|
| 769 |
-
while len(task_queue) > 0:
|
| 770 |
-
#if state.interrupted: interrupted = True ; break
|
| 771 |
-
|
| 772 |
-
# DEBUG: current task
|
| 773 |
-
# print('Running task: ', task_queue[0][0], ' on tile ', i, '/', num_tiles, ' with shape ', tile.shape)
|
| 774 |
-
task = task_queue.pop(0)
|
| 775 |
-
if task[0] == 'pre_norm':
|
| 776 |
-
group_norm_param.add_tile(tile, task[1])
|
| 777 |
-
break
|
| 778 |
-
elif task[0] == 'store_res' or task[0] == 'store_res_cpu':
|
| 779 |
-
task_id = 0
|
| 780 |
-
res = task[1](tile)
|
| 781 |
-
if not self.fast_mode or task[0] == 'store_res_cpu':
|
| 782 |
-
res = res.cpu()
|
| 783 |
-
while task_queue[task_id][0] != 'add_res':
|
| 784 |
-
task_id += 1
|
| 785 |
-
task_queue[task_id][1] = res
|
| 786 |
-
elif task[0] == 'add_res':
|
| 787 |
-
tile += task[1].to(device)
|
| 788 |
-
task[1] = None
|
| 789 |
-
else:
|
| 790 |
-
tile = task[1](tile)
|
| 791 |
-
pbar.update(1)
|
| 792 |
-
|
| 793 |
-
if interrupted: break
|
| 794 |
-
|
| 795 |
-
# check for NaNs in the tile.
|
| 796 |
-
# If there are NaNs, we abort the process to save user's time
|
| 797 |
-
#devices.test_for_nans(tile, "vae")
|
| 798 |
-
|
| 799 |
-
#print(tiles[i].shape, tile.shape, i, num_tiles)
|
| 800 |
-
if len(task_queue) == 0:
|
| 801 |
-
tiles[i] = None
|
| 802 |
-
num_completed += 1
|
| 803 |
-
if result is None: # NOTE: dim C varies from different cases, can only be inited dynamically
|
| 804 |
-
result = torch.zeros((N, tile.shape[1], height * 8 if is_decoder else height // 8, width * 8 if is_decoder else width // 8), device=device, requires_grad=False)
|
| 805 |
-
result[:, :, out_bboxes[i][2]:out_bboxes[i][3], out_bboxes[i][0]:out_bboxes[i][1]] = crop_valid_region(tile, in_bboxes[i], out_bboxes[i], is_decoder)
|
| 806 |
-
del tile
|
| 807 |
-
elif i == num_tiles - 1 and forward:
|
| 808 |
-
forward = False
|
| 809 |
-
tiles[i] = tile
|
| 810 |
-
elif i == 0 and not forward:
|
| 811 |
-
forward = True
|
| 812 |
-
tiles[i] = tile
|
| 813 |
-
else:
|
| 814 |
-
tiles[i] = tile.cpu()
|
| 815 |
-
del tile
|
| 816 |
-
|
| 817 |
-
if interrupted: break
|
| 818 |
-
if num_completed == num_tiles: break
|
| 819 |
-
|
| 820 |
-
# insert the group norm task to the head of each task queue
|
| 821 |
-
group_norm_func = group_norm_param.summary()
|
| 822 |
-
if group_norm_func is not None:
|
| 823 |
-
for i in range(num_tiles):
|
| 824 |
-
task_queue = task_queues[i]
|
| 825 |
-
task_queue.insert(0, ('apply_norm', group_norm_func))
|
| 826 |
-
|
| 827 |
-
# Done!
|
| 828 |
-
pbar.close()
|
| 829 |
-
return result if result is not None else result_approx.to(device)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video.py
DELETED
|
@@ -1,57 +0,0 @@
|
|
| 1 |
-
"""Render the zoom levels into one continuous zoom clip.
|
| 2 |
-
|
| 3 |
-
Each segment pushes in on level i until its frame is exactly the blurry crop that level i+1
|
| 4 |
-
was made from, then holds still and cross-fades blurry into sharp. The hold is the point:
|
| 5 |
-
it is where the viewer sees what the model actually added.
|
| 6 |
-
"""
|
| 7 |
-
import tempfile
|
| 8 |
-
|
| 9 |
-
import imageio.v2 as imageio
|
| 10 |
-
import numpy as np
|
| 11 |
-
from PIL import Image
|
| 12 |
-
|
| 13 |
-
from geometry import window_rect
|
| 14 |
-
|
| 15 |
-
FPS = 30
|
| 16 |
-
PUSH_FRAMES = 30
|
| 17 |
-
FADE_FRAMES = 14
|
| 18 |
-
TAIL_FRAMES = 24
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def _ease(t):
|
| 22 |
-
return t * t * (3 - 2 * t)
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
def _lerp_rect(a, b, t):
|
| 26 |
-
return tuple(round(p + (q - p) * t) for p, q in zip(a, b))
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
def _frames(levels, upscale, center):
|
| 30 |
-
size = levels[0].size
|
| 31 |
-
full = (0, 0, size[0], size[1])
|
| 32 |
-
for i in range(len(levels) - 1):
|
| 33 |
-
target = window_rect(size, upscale, center)
|
| 34 |
-
for f in range(PUSH_FRAMES):
|
| 35 |
-
# f / (PUSH_FRAMES - 1), not f / PUSH_FRAMES: the last frame has to land exactly on
|
| 36 |
-
# `target`, which is the crop the next level was built from. Otherwise it jumps.
|
| 37 |
-
rect = _lerp_rect(full, target, _ease(f / (PUSH_FRAMES - 1)))
|
| 38 |
-
yield levels[i].crop(rect).resize(size, Image.BICUBIC)
|
| 39 |
-
blurry = np.asarray(levels[i].crop(target).resize(size, Image.BICUBIC), dtype=np.float32)
|
| 40 |
-
sharp = np.asarray(levels[i + 1], dtype=np.float32)
|
| 41 |
-
for f in range(FADE_FRAMES):
|
| 42 |
-
a = _ease((f + 1) / FADE_FRAMES)
|
| 43 |
-
yield Image.fromarray((blurry * (1 - a) + sharp * a).astype(np.uint8))
|
| 44 |
-
for _ in range(TAIL_FRAMES):
|
| 45 |
-
yield levels[-1]
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
def render(levels, upscale=4, center=(0.5, 0.5)):
|
| 49 |
-
"""Write an mp4 of the zoom and return its path. Needs at least two levels."""
|
| 50 |
-
if len(levels) < 2:
|
| 51 |
-
return None
|
| 52 |
-
path = tempfile.mkstemp(suffix=".mp4")[1]
|
| 53 |
-
with imageio.get_writer(path, fps=FPS, codec="libx264", quality=8,
|
| 54 |
-
pixelformat="yuv420p", macro_block_size=1) as out:
|
| 55 |
-
for frame in _frames(levels, upscale, center):
|
| 56 |
-
out.append_data(np.asarray(frame.convert("RGB")))
|
| 57 |
-
return path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
zoom.py
DELETED
|
@@ -1,138 +0,0 @@
|
|
| 1 |
-
"""OracleZoom recursive zoom, one image at a time, all in memory.
|
| 2 |
-
|
| 3 |
-
Flattened from src/opd_zoom/teacher/oracle_infer.py for the Space. Three differences, none
|
| 4 |
-
of them to the model: no disk round-trip between recursions, the zoom window can sit
|
| 5 |
-
anywhere instead of only the centre, and the loop is a generator so the UI can show each
|
| 6 |
-
level the moment it lands. Everything on one device, which is what ZeroGPU gives us.
|
| 7 |
-
"""
|
| 8 |
-
import gc
|
| 9 |
-
import os
|
| 10 |
-
import sys
|
| 11 |
-
|
| 12 |
-
import torch
|
| 13 |
-
from PIL import Image
|
| 14 |
-
from torchvision import transforms
|
| 15 |
-
|
| 16 |
-
from geometry import PROCESS_SIZE, resize_and_center_crop, zoom_window
|
| 17 |
-
|
| 18 |
-
VENDOR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "vendor")
|
| 19 |
-
if VENDOR not in sys.path:
|
| 20 |
-
sys.path.insert(0, VENDOR)
|
| 21 |
-
|
| 22 |
-
REPO = "dipta007/OracleZoom"
|
| 23 |
-
SD3 = "stabilityai/stable-diffusion-3-medium-diffusers"
|
| 24 |
-
VLM = "Qwen/Qwen2.5-VL-3B-Instruct"
|
| 25 |
-
COZ_PROMPT = (
|
| 26 |
-
"The second image is a zoom-in of the first image. Based on this knowledge, "
|
| 27 |
-
"what is in the second image? Give me a set of words."
|
| 28 |
-
)
|
| 29 |
-
_to_tensor = transforms.Compose([transforms.ToTensor()])
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
class _SRArgs:
|
| 33 |
-
def __init__(self, coz_ckpt):
|
| 34 |
-
self.lora_path = f"{coz_ckpt}/SR_LoRA/model_20001.pkl"
|
| 35 |
-
self.vae_path = f"{coz_ckpt}/SR_VAE/vae_encoder_20001.pt"
|
| 36 |
-
self.pretrained_model_name_or_path = SD3
|
| 37 |
-
self.process_size = PROCESS_SIZE
|
| 38 |
-
self.lora_rank = 4
|
| 39 |
-
self.merge_and_unload_lora = False
|
| 40 |
-
self.mixed_precision = "fp16"
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
class Models:
|
| 44 |
-
"""Built once at import, read-only afterwards. Concurrent requests only read."""
|
| 45 |
-
|
| 46 |
-
def __init__(self, weights=None):
|
| 47 |
-
from huggingface_hub import snapshot_download
|
| 48 |
-
|
| 49 |
-
w = weights or snapshot_download(repo_id=REPO)
|
| 50 |
-
self.vlm, self.proc, self.process_vision_info = _build_vlm(f"{w}/ckpt/VLM_LoRA/checkpoint-10000")
|
| 51 |
-
self.sr = _build_sr(f"{w}/ckpt", f"{w}/merged_transformer.safetensors")
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
def _build_vlm(lora_path):
|
| 55 |
-
from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
|
| 56 |
-
from qwen_vl_utils import process_vision_info
|
| 57 |
-
from peft import PeftModel
|
| 58 |
-
|
| 59 |
-
# device_map="auto" asks accelerate to read real GPU memory, which does not exist at
|
| 60 |
-
# module scope on ZeroGPU. Place it ourselves. sdpa because flash-attn has no wheel here.
|
| 61 |
-
model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
|
| 62 |
-
VLM, torch_dtype=torch.bfloat16, attn_implementation="sdpa"
|
| 63 |
-
)
|
| 64 |
-
# torch_device="cpu" is load-bearing. Left to itself peft calls infer_device(), which reads
|
| 65 |
-
# the cuda flag ZeroGPU patches to True, then hands "cuda" to safetensors. That builds
|
| 66 |
-
# tensors straight on a device that is not attached yet: RuntimeError, no CUDA GPUs.
|
| 67 |
-
model = PeftModel.from_pretrained(model, lora_path, torch_device="cpu").merge_and_unload()
|
| 68 |
-
return model.eval().to("cuda"), AutoProcessor.from_pretrained(VLM), process_vision_info
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
def _build_sr(coz_ckpt, merged_transformer):
|
| 72 |
-
from safetensors.torch import load_file
|
| 73 |
-
from osediff_sd3 import OSEDiff_SD3_TEST, SD3Euler
|
| 74 |
-
|
| 75 |
-
# Assemble entirely on CPU, then move once. Two places here materialise weights straight
|
| 76 |
-
# onto the model's own device (inject_lora's torch.load map_location, and safetensors), and
|
| 77 |
-
# on ZeroGPU that device reads as cuda while no GPU is attached yet.
|
| 78 |
-
sr = SD3Euler(device="cpu")
|
| 79 |
-
# Construct first, load second. OSEDiff_SD3_TEST swaps every targeted Linear for a
|
| 80 |
-
# LoraInjectedLinear, which renames the keys. Our merged checkpoint was saved after that
|
| 81 |
-
# swap, so loading it earlier matches nothing and silently leaves the base transformer.
|
| 82 |
-
test = OSEDiff_SD3_TEST(_SRArgs(coz_ckpt), sr)
|
| 83 |
-
sd = load_file(merged_transformer)
|
| 84 |
-
missing, unexpected = test.model.transformer.load_state_dict(
|
| 85 |
-
{k: v.to(torch.float32) for k, v in sd.items()}, strict=False)
|
| 86 |
-
if len(unexpected) > len(sd) // 2:
|
| 87 |
-
raise RuntimeError(f"merged transformer did not match: {len(unexpected)} of {len(sd)} "
|
| 88 |
-
f"keys unexpected. LoRA injection order or checkpoint is wrong.")
|
| 89 |
-
print(f"#### merged transformer loaded (missing {len(missing)} unexpected {len(unexpected)})")
|
| 90 |
-
del sd
|
| 91 |
-
gc.collect()
|
| 92 |
-
|
| 93 |
-
for m in (sr.text_enc_1, sr.text_enc_2, sr.text_enc_3):
|
| 94 |
-
m.to("cuda")
|
| 95 |
-
sr.transformer.to("cuda", dtype=torch.float32)
|
| 96 |
-
sr.vae.to("cuda", dtype=torch.float32)
|
| 97 |
-
for m in (sr.text_enc_1, sr.text_enc_2, sr.text_enc_3, sr.transformer, sr.vae):
|
| 98 |
-
m.requires_grad_(False)
|
| 99 |
-
sr.device = "cuda" # encode_prompt and set_timesteps read this, not the module devices
|
| 100 |
-
return test
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
def write_prompt(models, first, second, max_new_tokens=32):
|
| 104 |
-
messages = [
|
| 105 |
-
{"role": "system", "content": COZ_PROMPT},
|
| 106 |
-
{"role": "user", "content": [{"type": "image", "image": first},
|
| 107 |
-
{"type": "image", "image": second}]},
|
| 108 |
-
]
|
| 109 |
-
text = models.proc.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
| 110 |
-
images, videos = models.process_vision_info(messages)
|
| 111 |
-
inputs = models.proc(text=[text], images=images, videos=videos,
|
| 112 |
-
padding=True, return_tensors="pt").to("cuda")
|
| 113 |
-
gen = models.vlm.generate(**inputs, max_new_tokens=max_new_tokens)
|
| 114 |
-
trimmed = [o[len(i):] for i, o in zip(inputs.input_ids, gen)]
|
| 115 |
-
return models.proc.batch_decode(trimmed, skip_special_tokens=True,
|
| 116 |
-
clean_up_tokenization_spaces=False)[0].strip()
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
def super_resolve(models, img, prompt):
|
| 120 |
-
lq = _to_tensor(img).unsqueeze(0).to("cuda") * 2 - 1
|
| 121 |
-
with torch.no_grad():
|
| 122 |
-
out = torch.clamp(models.sr(lq, prompt=prompt)[0].cpu(), -1.0, 1.0)
|
| 123 |
-
return transforms.ToPILImage()(out * 0.5 + 0.5)
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
def zoom(models, image, levels=4, upscale=4, center=(0.5, 0.5)):
|
| 127 |
-
"""Yield one (level, factor, prompt, blurry_input, result) per recursion.
|
| 128 |
-
|
| 129 |
-
`blurry_input` is the plain bicubic enlargement the model starts from. It is what the
|
| 130 |
-
super-resolution has to improve on, so it doubles as the honest before-picture.
|
| 131 |
-
"""
|
| 132 |
-
cur = resize_and_center_crop(image)
|
| 133 |
-
yield 0, 1, "", cur, cur
|
| 134 |
-
for i in range(levels):
|
| 135 |
-
blurry = zoom_window(cur, upscale, center).resize(cur.size, Image.BICUBIC)
|
| 136 |
-
prompt = write_prompt(models, cur, blurry)
|
| 137 |
-
cur = super_resolve(models, blurry, prompt)
|
| 138 |
-
yield i + 1, upscale ** (i + 1), prompt, blurry, cur
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|