Spaces:
Running on Zero
Running on Zero
File size: 6,344 Bytes
fcc874d 54b7620 fcc874d 54b7620 fcc874d 54b7620 fcc874d 54b7620 fcc874d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | """OracleZoom recursive zoom, one image at a time, all in memory.
Flattened from src/opd_zoom/teacher/oracle_infer.py for the Space. Three differences, none
of them to the model: no disk round-trip between recursions, the zoom window can sit
anywhere instead of only the centre, and the loop is a generator so the UI can show each
level the moment it lands. Everything on one device, which is what ZeroGPU gives us.
"""
import gc
import os
import sys
import torch
from PIL import Image
from torchvision import transforms
from geometry import PROCESS_SIZE, resize_and_center_crop, zoom_window
VENDOR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "vendor")
if VENDOR not in sys.path:
sys.path.insert(0, VENDOR)
REPO = "dipta007/OracleZoom"
SD3 = "stabilityai/stable-diffusion-3-medium-diffusers"
VLM = "Qwen/Qwen2.5-VL-3B-Instruct"
COZ_PROMPT = (
"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."
)
_to_tensor = transforms.Compose([transforms.ToTensor()])
class _SRArgs:
def __init__(self, coz_ckpt):
self.lora_path = f"{coz_ckpt}/SR_LoRA/model_20001.pkl"
self.vae_path = f"{coz_ckpt}/SR_VAE/vae_encoder_20001.pt"
self.pretrained_model_name_or_path = SD3
self.process_size = PROCESS_SIZE
self.lora_rank = 4
self.merge_and_unload_lora = False
self.mixed_precision = "fp16"
class Models:
"""Built once at import, read-only afterwards. Concurrent requests only read."""
def __init__(self, weights=None):
from huggingface_hub import snapshot_download
w = weights or snapshot_download(repo_id=REPO)
self.vlm, self.proc, self.process_vision_info = _build_vlm(f"{w}/ckpt/VLM_LoRA/checkpoint-10000")
self.sr = _build_sr(f"{w}/ckpt", f"{w}/merged_transformer.safetensors")
def _build_vlm(lora_path):
from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
from qwen_vl_utils import process_vision_info
from peft import PeftModel
# device_map="auto" asks accelerate to read real GPU memory, which does not exist at
# module scope on ZeroGPU. Place it ourselves. sdpa because flash-attn has no wheel here.
model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
VLM, torch_dtype=torch.bfloat16, attn_implementation="sdpa"
)
# torch_device="cpu" is load-bearing. Left to itself peft calls infer_device(), which reads
# the cuda flag ZeroGPU patches to True, then hands "cuda" to safetensors. That builds
# tensors straight on a device that is not attached yet: RuntimeError, no CUDA GPUs.
model = PeftModel.from_pretrained(model, lora_path, torch_device="cpu").merge_and_unload()
return model.eval().to("cuda"), AutoProcessor.from_pretrained(VLM), process_vision_info
def _build_sr(coz_ckpt, merged_transformer):
from safetensors.torch import load_file
from osediff_sd3 import OSEDiff_SD3_TEST, SD3Euler
# Assemble entirely on CPU, then move once. Two places here materialise weights straight
# onto the model's own device (inject_lora's torch.load map_location, and safetensors), and
# on ZeroGPU that device reads as cuda while no GPU is attached yet.
sr = SD3Euler(device="cpu")
# Construct first, load second. OSEDiff_SD3_TEST swaps every targeted Linear for a
# LoraInjectedLinear, which renames the keys. Our merged checkpoint was saved after that
# swap, so loading it earlier matches nothing and silently leaves the base transformer.
test = OSEDiff_SD3_TEST(_SRArgs(coz_ckpt), sr)
sd = load_file(merged_transformer)
missing, unexpected = test.model.transformer.load_state_dict(
{k: v.to(torch.float32) for k, v in sd.items()}, strict=False)
if len(unexpected) > len(sd) // 2:
raise RuntimeError(f"merged transformer did not match: {len(unexpected)} of {len(sd)} "
f"keys unexpected. LoRA injection order or checkpoint is wrong.")
print(f"#### merged transformer loaded (missing {len(missing)} unexpected {len(unexpected)})")
del sd
gc.collect()
for m in (sr.text_enc_1, sr.text_enc_2, sr.text_enc_3):
m.to("cuda")
sr.transformer.to("cuda", dtype=torch.float32)
sr.vae.to("cuda", dtype=torch.float32)
for m in (sr.text_enc_1, sr.text_enc_2, sr.text_enc_3, sr.transformer, sr.vae):
m.requires_grad_(False)
sr.device = "cuda" # encode_prompt and set_timesteps read this, not the module devices
return test
def write_prompt(models, first, second, max_new_tokens=32):
messages = [
{"role": "system", "content": COZ_PROMPT},
{"role": "user", "content": [{"type": "image", "image": first},
{"type": "image", "image": second}]},
]
text = models.proc.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
images, videos = models.process_vision_info(messages)
inputs = models.proc(text=[text], images=images, videos=videos,
padding=True, return_tensors="pt").to("cuda")
gen = models.vlm.generate(**inputs, max_new_tokens=max_new_tokens)
trimmed = [o[len(i):] for i, o in zip(inputs.input_ids, gen)]
return models.proc.batch_decode(trimmed, skip_special_tokens=True,
clean_up_tokenization_spaces=False)[0].strip()
def super_resolve(models, img, prompt):
lq = _to_tensor(img).unsqueeze(0).to("cuda") * 2 - 1
with torch.no_grad():
out = torch.clamp(models.sr(lq, prompt=prompt)[0].cpu(), -1.0, 1.0)
return transforms.ToPILImage()(out * 0.5 + 0.5)
def zoom(models, image, levels=4, upscale=4, center=(0.5, 0.5)):
"""Yield one (level, factor, prompt, blurry_input, result) per recursion.
`blurry_input` is the plain bicubic enlargement the model starts from. It is what the
super-resolution has to improve on, so it doubles as the honest before-picture.
"""
cur = resize_and_center_crop(image)
yield 0, 1, "", cur, cur
for i in range(levels):
blurry = zoom_window(cur, upscale, center).resize(cur.size, Image.BICUBIC)
prompt = write_prompt(models, cur, blurry)
cur = super_resolve(models, blurry, prompt)
yield i + 1, upscale ** (i + 1), prompt, blurry, cur
|