prithivMLmods commited on
Commit
49036b2
Β·
verified Β·
1 Parent(s): e12e49c

update app

Browse files
Files changed (2) hide show
  1. app.py +802 -0
  2. requirements.txt +31 -0
app.py ADDED
@@ -0,0 +1,802 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import subprocess
4
+ import tempfile
5
+
6
+ import torch
7
+ import numpy as np
8
+ import gradio as gr
9
+ from PIL import Image
10
+ from types import SimpleNamespace
11
+ from huggingface_hub import snapshot_download
12
+
13
+ import spaces
14
+
15
+ from typing import Iterable
16
+
17
+ # --------------------------- theme ---------------------------
18
+
19
+ from gradio.themes import Soft
20
+ from gradio.themes.utils import colors, fonts, sizes
21
+
22
+ colors.orange_red = colors.Color(
23
+ name="orange_red", c50="#FFF0E5", c100="#FFE0CC", c200="#FFC299", c300="#FFA366",
24
+ c400="#FF8533", c500="#FF4500", c600="#E63E00", c700="#CC3700", c800="#B33000",
25
+ c900="#992900", c950="#802200",
26
+ )
27
+
28
+ class OrangeRedTheme(Soft):
29
+ def __init__(
30
+ self, *, primary_hue: colors.Color | str = colors.gray,
31
+ secondary_hue: colors.Color | str = colors.orange_red,
32
+ neutral_hue: colors.Color | str = colors.slate, text_size: sizes.Size | str = sizes.text_lg,
33
+ font: fonts.Font | str | Iterable[fonts.Font | str] = (
34
+ fonts.GoogleFont("Outfit"), "Arial", "sans-serif",
35
+ ),
36
+ font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (
37
+ fonts.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace",
38
+ ),
39
+ ):
40
+ super().__init__(
41
+ primary_hue=primary_hue, secondary_hue=secondary_hue, neutral_hue=neutral_hue,
42
+ text_size=text_size, font=font, font_mono=font_mono,
43
+ )
44
+ super().set(
45
+ background_fill_primary="*primary_50",
46
+ background_fill_primary_dark="*primary_900",
47
+ body_background_fill="linear-gradient(135deg, *primary_200, *primary_100)",
48
+ body_background_fill_dark="linear-gradient(135deg, *primary_900, *primary_800)",
49
+ button_primary_text_color="white",
50
+ button_primary_text_color_hover="white",
51
+ button_primary_background_fill="linear-gradient(90deg, *secondary_500, *secondary_600)",
52
+ button_primary_background_fill_hover="linear-gradient(90deg, *secondary_600, *secondary_700)",
53
+ button_primary_background_fill_dark="linear-gradient(90deg, *secondary_600, *secondary_700)",
54
+ button_primary_background_fill_hover_dark="linear-gradient(90deg, *secondary_500, *secondary_600)",
55
+ slider_color="*secondary_500",
56
+ slider_color_dark="*secondary_600",
57
+ block_title_text_weight="600", block_border_width="3px",
58
+ block_shadow="*shadow_drop_lg", button_primary_shadow="*shadow_drop_lg",
59
+ button_large_padding="11px", color_accent_soft="*primary_100",
60
+ block_label_background_fill="*primary_200",
61
+ )
62
+
63
+ orange_red_theme = OrangeRedTheme()
64
+
65
+ # --------------------------- theme ---------------------------
66
+
67
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
68
+
69
+ print("CUDA_VISIBLE_DEVICES=", os.environ.get("CUDA_VISIBLE_DEVICES"))
70
+ print("torch.__version__ =", torch.__version__)
71
+ print("torch.version.cuda =", torch.version.cuda)
72
+ print("cuda available:", torch.cuda.is_available())
73
+ print("cuda device count:", torch.cuda.device_count())
74
+ if torch.cuda.is_available():
75
+ print("current device:", torch.cuda.current_device())
76
+ print("device name:", torch.cuda.get_device_name(torch.cuda.current_device()))
77
+
78
+ print("Using device:", device)
79
+
80
+ os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
81
+
82
+ PID_REPO_URL = "https://github.com/nv-tlabs/PiD.git"
83
+ PID_REPO_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PiD")
84
+
85
+ if not os.path.exists(PID_REPO_DIR):
86
+ print(f"[pid] cloning {PID_REPO_URL} -> {PID_REPO_DIR}", flush=True)
87
+ subprocess.check_call(["git", "clone", "--depth", "1", PID_REPO_URL, PID_REPO_DIR])
88
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "-e", PID_REPO_DIR])
89
+
90
+ os.chdir(PID_REPO_DIR)
91
+ sys.path.insert(0, PID_REPO_DIR)
92
+
93
+ # NOTE (fix):
94
+ # The "2kto4k" 4K decoder for the zimage backbone is registered under the key
95
+ # "2kto4k_v1pt5" (zimage aliases to the "flux" backbone's 2kto4k_v1pt5 entry
96
+ # in pid/_src/inference/checkpoint_registry.py). The actual checkpoint lives in
97
+ # checkpoints/PiD_v1pt5_res2kto4k_sr4x_official_flux_distill_4step/ (note the
98
+ # "v1pt5_" prefix) β€” NOT checkpoints/PiD_res2kto4k_sr4x_official_flux_distill_4step/,
99
+ # which is the legacy v1 folder used only by sd3/sdxl. Both the ckpt_type
100
+ # string used below in _load_pid() and this download pattern must match that
101
+ # folder name, or you'll trade the KeyError for a FileNotFoundError.
102
+ snapshot_download(
103
+ repo_id="nvidia/PiD",
104
+ local_dir=PID_REPO_DIR,
105
+ allow_patterns=[
106
+ "checkpoints/PiD_res2k_sr4x_official_flux_distill_4step/*",
107
+ "checkpoints/PiD_v1pt5_res2kto4k_sr4x_official_flux_distill_4step/*",
108
+ "checkpoints/ae.safetensors",
109
+ ],
110
+ )
111
+
112
+ from pid._src.inference.checkpoint_registry import get_pid_checkpoint
113
+ from pid._src.inference.pipeline_registry import (
114
+ decode_with_pipeline_vae,
115
+ extract_latent,
116
+ load_pipeline,
117
+ )
118
+ from pid._src.utils.model_loader import load_model_from_checkpoint
119
+
120
+ DTYPE = torch.bfloat16
121
+ BACKBONE = "zimage"
122
+ SR_SCALE = 4
123
+ PID_INFERENCE_STEPS = 4
124
+ MAX_SEED = 2**31 - 1
125
+
126
+ print("[pid] loading Z-Image pipeline...", flush=True)
127
+
128
+ from transformers import masking_utils as _mu
129
+
130
+ def _broadcasting_vmap_for_bhqkv(mask_function, bh_indices: bool = True):
131
+ def wrapped(b, h, q, k):
132
+ if bh_indices:
133
+ return mask_function(
134
+ b[:, None, None, None],
135
+ h[None, :, None, None],
136
+ q[None, None, :, None],
137
+ k[None, None, None, :],
138
+ )
139
+ return mask_function(b, h, q[:, None], k[None, :])
140
+ return wrapped
141
+
142
+ _mu._vmap_for_bhqkv = _broadcasting_vmap_for_bhqkv
143
+
144
+ import transformers.models.gemma2.modeling_gemma2 as _gm
145
+
146
+ _orig_gemma2_forward = _gm.Gemma2Model.forward
147
+
148
+ def _patched_gemma2_forward(self, *args, **kwargs):
149
+ _orig_tt = torch.tensor
150
+ dev = self.embed_tokens.weight.device
151
+ def _tt(data, *a, **kw):
152
+ kw.setdefault("device", dev)
153
+ return _orig_tt(data, *a, **kw)
154
+ torch.tensor = _tt
155
+ try:
156
+ return _orig_gemma2_forward(self, *args, **kwargs)
157
+ finally:
158
+ torch.tensor = _orig_tt
159
+
160
+ _gm.Gemma2Model.forward = _patched_gemma2_forward
161
+
162
+
163
+ pipeline, pipe_cfg = load_pipeline(BACKBONE, dtype=DTYPE)
164
+ pipeline.to("cuda")
165
+
166
+ print("[pid] loading TAEF1 (fast preview decoder)...", flush=True)
167
+ from diffusers import AutoencoderTiny
168
+
169
+ taef1 = AutoencoderTiny.from_pretrained(
170
+ "madebyollin/taef1", torch_dtype=DTYPE, low_cpu_mem_usage=False
171
+ ).to("cuda")
172
+ taef1.eval()
173
+
174
+
175
+ def _load_pid(ckpt_type: str):
176
+ meta = get_pid_checkpoint(BACKBONE, ckpt_type)
177
+ print(f"[pid] loading PiD decoder ({ckpt_type})...", flush=True)
178
+ model, _ = load_model_from_checkpoint(
179
+ experiment_name=meta.experiment,
180
+ checkpoint_path=meta.checkpoint_path,
181
+ config_file="pid/_src/configs/pid/config.py",
182
+ enable_fsdp=False,
183
+ strict=False,
184
+ )
185
+ model.eval()
186
+ return model
187
+
188
+
189
+ # NOTE (fix): valid VALID_CKPT_TYPES are ("2k", "2kto4k", "2kto4k_v1pt5").
190
+ # "2kto4k" (no suffix) is only registered for sd3/sdxl β€” for zimage the 4K
191
+ # decoder must be requested as "2kto4k_v1pt5". The dict key "2kto4k" below is
192
+ # just our own internal lookup label (used by _pick_pid_model) and is kept
193
+ # as-is for readability; only the argument passed to _load_pid() changes.
194
+ pid_models = {
195
+ "2k": _load_pid("2k"),
196
+ "2kto4k": _load_pid("2kto4k_v1pt5"),
197
+ }
198
+
199
+ print("[pid] loading FLUX.2-Klein pipeline...", flush=True)
200
+ from diffusers import Flux2KleinPipeline
201
+
202
+ klein_pipe = Flux2KleinPipeline.from_pretrained(
203
+ "black-forest-labs/FLUX.2-klein-4B",
204
+ torch_dtype=DTYPE,
205
+ ).to("cuda")
206
+ print("[pid] FLUX.2-Klein loaded.", flush=True)
207
+ print("[pid] ready", flush=True)
208
+
209
+ def _pick_pid_model(resolution: int):
210
+ return pid_models["2kto4k"] if resolution > 512 else pid_models["2k"]
211
+
212
+
213
+ def _taef1_preview(packed_latent: torch.Tensor, H: int, W: int) -> Image.Image:
214
+ with torch.no_grad():
215
+ unpacked = extract_latent(pipeline, SimpleNamespace(images=packed_latent), pipe_cfg, H, W)
216
+ scale = pipeline.vae.config.scaling_factor
217
+ shift = getattr(pipeline.vae.config, "shift_factor", None) or 0.0
218
+ denorm = unpacked.to(dtype=DTYPE) / scale + shift
219
+ img = taef1.decode(denorm).sample
220
+ img = (img.float().clamp(-1, 1) + 1) / 2
221
+ arr = (img[0].permute(1, 2, 0).cpu().numpy() * 255).astype(np.uint8)
222
+ return Image.fromarray(arr)
223
+
224
+
225
+ def _pid_pixel_to_pil(x: torch.Tensor) -> Image.Image:
226
+ arr = (
227
+ (x[0].float().clamp(-1, 1) + 1) * 127.5
228
+ ).permute(1, 2, 0).cpu().numpy().astype(np.uint8)
229
+ return Image.fromarray(arr)
230
+
231
+
232
+ def _pid_stream(
233
+ pid_model,
234
+ latent: torch.Tensor,
235
+ baseline_01: torch.Tensor,
236
+ sigma: float,
237
+ caption: str,
238
+ num_steps: int = PID_INFERENCE_STEPS,
239
+ ):
240
+ from contextlib import nullcontext
241
+
242
+ B = 1
243
+ lq_h, lq_w = baseline_01.shape[-2], baseline_01.shape[-1]
244
+ img_h, img_w = lq_h * SR_SCALE, lq_w * SR_SCALE
245
+
246
+ caption_embs, _ = pid_model._encode_text_raw([caption])
247
+ caption_embs = caption_embs.to(**pid_model.tensor_kwargs)
248
+
249
+ lq_video_or_image = (baseline_01 * 2.0 - 1.0).to(dtype=DTYPE, device="cuda")
250
+ lq_latent = latent.to(dtype=DTYPE, device="cuda")
251
+ degrade_sigma_tensor = torch.tensor([sigma], device="cuda", dtype=torch.float32)
252
+
253
+ gen = torch.Generator(device="cuda").manual_seed(0)
254
+ noise = torch.randn(B, 3, img_h, img_w, device="cuda", generator=gen)
255
+
256
+ t_list = pid_model._get_t_list(device=torch.device("cuda"), num_steps=num_steps)
257
+ autocast_ctx = (
258
+ torch.autocast("cuda", dtype=pid_model.autocast_dtype)
259
+ if pid_model.autocast_dtype
260
+ else nullcontext()
261
+ )
262
+ net = pid_model.net
263
+ net.eval()
264
+ timescale = pid_model.fm_trainer.timescale
265
+ student_sample_type = pid_model.config.student_sample_type
266
+ prediction_type = pid_model.config.prediction_type
267
+
268
+ x = noise
269
+ with torch.no_grad(), autocast_ctx:
270
+ steps_total = len(t_list) - 1
271
+ for step_idx, (t_cur, t_next) in enumerate(zip(t_list[:-1], t_list[1:])):
272
+ t_cur_batch = t_cur.expand(B)
273
+ t_cur_scaled = t_cur_batch * timescale
274
+ v_pred = net(
275
+ x,
276
+ t_cur_scaled,
277
+ caption_embs,
278
+ lq_video_or_image=lq_video_or_image,
279
+ lq_latent=lq_latent,
280
+ degrade_sigma=degrade_sigma_tensor,
281
+ )
282
+ if t_next.item() > 0:
283
+ if student_sample_type == "ode":
284
+ v_for_step = pid_model._net_output_to_velocity(
285
+ x, v_pred, t_cur_batch, prediction_type
286
+ )
287
+ dt = t_next - t_cur
288
+ x = x + dt * v_for_step
289
+ else:
290
+ x0_pred = pid_model._velocity_to_x0(x, v_pred, t_cur_batch)
291
+ eps_infer = torch.randn(
292
+ x0_pred.shape, device=x0_pred.device,
293
+ dtype=x0_pred.dtype, generator=gen,
294
+ )
295
+ s = [B] + [1] * (x.ndim - 1)
296
+ t_next_bcast = t_next.reshape(1).expand(s)
297
+ x = (1.0 - t_next_bcast) * x0_pred + t_next_bcast * eps_infer
298
+ else:
299
+ x = pid_model._velocity_to_x0(x, v_pred, t_cur_batch)
300
+ yield step_idx + 1, steps_total, x.clone()
301
+
302
+
303
+ def _resize_to_divisible(image: Image.Image, max_side: int = 1024, div: int = 16) -> Image.Image:
304
+ w, h = image.size
305
+ scale = min(max_side / w, max_side / h, 1.0)
306
+ nw = max(div, (int(w * scale) // div) * div)
307
+ nh = max(div, (int(h * scale) // div) * div)
308
+ return image.resize((nw, nh), Image.LANCZOS)
309
+
310
+
311
+ def _encode_image_to_latent(image_01: torch.Tensor) -> torch.Tensor:
312
+ vae = pipeline.vae
313
+ image_norm = image_01 * 2.0 - 1.0
314
+ with torch.no_grad():
315
+ latent = vae.encode(image_norm.to(dtype=DTYPE, device="cuda")).latent_dist.sample()
316
+ scale = vae.config.scaling_factor
317
+ shift = getattr(vae.config, "shift_factor", None) or 0.0
318
+ latent = (latent - shift) * scale
319
+ return latent
320
+
321
+ import random
322
+ import threading
323
+ import queue as _queue
324
+
325
+
326
+ def _generate_core(
327
+ prompt: str,
328
+ num_inference_steps: int = 28,
329
+ guidance_scale: float = 5.0,
330
+ seed: int = 0,
331
+ resolution: int = 512,
332
+ randomize_seed: bool = False,
333
+ ):
334
+ if not prompt or not prompt.strip():
335
+ raise gr.Error("Please enter a prompt.")
336
+
337
+ if randomize_seed:
338
+ seed = random.randint(0, 2**31 - 1)
339
+ seed = int(seed)
340
+ num_inference_steps = int(num_inference_steps)
341
+ H = W = int(resolution)
342
+
343
+ yield (
344
+ gr.update(visible=True, value=None, label="Generating Z-Image…"),
345
+ gr.update(visible=False, value=None),
346
+ gr.update(value=seed),
347
+ )
348
+
349
+ preview_q: "_queue.Queue" = _queue.Queue()
350
+ _DONE = object()
351
+
352
+ def streaming_cb(pipe, step_index, timestep, callback_kwargs):
353
+ try:
354
+ preview = _taef1_preview(callback_kwargs["latents"], H, W)
355
+ preview_q.put((step_index, preview))
356
+ except Exception as e:
357
+ print(f"[pid] taef1 preview failed at step {step_index}: {e}", flush=True)
358
+ return callback_kwargs
359
+
360
+ def run_pipeline():
361
+ gen_torch = torch.Generator(device="cuda").manual_seed(int(seed))
362
+ gen_kwargs = dict(
363
+ prompt=prompt, height=H, width=W,
364
+ num_inference_steps=num_inference_steps,
365
+ guidance_scale=float(guidance_scale),
366
+ num_images_per_prompt=1,
367
+ output_type="latent",
368
+ generator=gen_torch,
369
+ callback_on_step_end=streaming_cb,
370
+ callback_on_step_end_tensor_inputs=["latents"],
371
+ )
372
+ gen_kwargs.update(pipe_cfg.extra_generate_kwargs)
373
+ try:
374
+ with torch.no_grad():
375
+ out = pipeline(**gen_kwargs)
376
+ preview_q.put((_DONE, out))
377
+ except Exception as e:
378
+ preview_q.put((_DONE, e))
379
+
380
+ thread = threading.Thread(target=run_pipeline, daemon=True)
381
+ thread.start()
382
+
383
+ raw_output = None
384
+ while True:
385
+ step_index, payload = preview_q.get()
386
+ if step_index is _DONE:
387
+ if isinstance(payload, Exception):
388
+ raise payload
389
+ raw_output = payload
390
+ break
391
+ label = f"Generating Z-Image β€” step {step_index + 1}/{num_inference_steps}"
392
+ yield (
393
+ gr.update(visible=True, value=payload, label=label),
394
+ gr.update(visible=False),
395
+ gr.update(),
396
+ )
397
+
398
+ thread.join()
399
+ final_latent = extract_latent(pipeline, raw_output, pipe_cfg, H, W)
400
+
401
+ yield (
402
+ gr.update(visible=True, label="Decoding final Z-Image…"),
403
+ gr.update(visible=False),
404
+ gr.update(),
405
+ )
406
+ with torch.no_grad():
407
+ baseline_01 = decode_with_pipeline_vae(pipeline, final_latent, pipe_cfg)
408
+ zimage_img = Image.fromarray(
409
+ (baseline_01[0].clamp(0, 1).permute(1, 2, 0).float().cpu().numpy() * 255).astype(np.uint8)
410
+ )
411
+
412
+ torch.cuda.empty_cache()
413
+
414
+ final_sigma = float(pipeline.scheduler.sigmas[-1].item())
415
+ pid_img = None
416
+ pid_model = _pick_pid_model(H)
417
+
418
+ for k, total, x in _pid_stream(pid_model, final_latent, baseline_01, final_sigma, prompt):
419
+ pid_img = _pid_pixel_to_pil(x)
420
+ yield (
421
+ gr.update(visible=True, value=pid_img, label=f"Upscaling with PiD β€” step {k}/{total}"),
422
+ gr.update(visible=False),
423
+ gr.update(),
424
+ )
425
+
426
+ yield (
427
+ gr.update(visible=False, value=None),
428
+ gr.update(visible=True, value=(zimage_img, pid_img)),
429
+ gr.update(),
430
+ )
431
+
432
+
433
+ @spaces.GPU(duration=60)
434
+ def generate_large(*args, **kwargs):
435
+ yield from _generate_core(*args, **kwargs)
436
+
437
+
438
+ @spaces.GPU(duration=90, size="xlarge")
439
+ def generate_xlarge(*args, **kwargs):
440
+ yield from _generate_core(*args, **kwargs)
441
+
442
+
443
+ def generate(prompt, num_inference_steps, guidance_scale, seed, resolution, randomize_seed):
444
+ fn = generate_xlarge if int(resolution) >= 1024 else generate_large
445
+ yield from fn(prompt, num_inference_steps, guidance_scale, seed, resolution, randomize_seed)
446
+
447
+
448
+ def update_dimensions_on_upload(image: Image.Image):
449
+ if image is None:
450
+ return "_Upload an image to see its processed dimensions._"
451
+ resized = _resize_to_divisible(image)
452
+ ow, oh = image.size
453
+ nw, nh = resized.size
454
+ return (
455
+ f"**Input:** {ow} Γ— {oh} px β†’ "
456
+ f"**Processed:** {nw} Γ— {nh} px β†’ "
457
+ f"**PiD output:** {nw * SR_SCALE} Γ— {nh * SR_SCALE} px"
458
+ )
459
+
460
+
461
+ def _i2i_generate_core(
462
+ input_image: Image.Image,
463
+ prompt: str,
464
+ seed: int = 0,
465
+ randomize_seed: bool = True,
466
+ guidance_scale: float = 1.0,
467
+ steps: int = 4,
468
+ ):
469
+ if input_image is None:
470
+ raise gr.Error("Please upload an input image.")
471
+ if not prompt or not prompt.strip():
472
+ raise gr.Error("Please enter a prompt / description.")
473
+
474
+ if randomize_seed:
475
+ seed = random.randint(0, MAX_SEED)
476
+ seed = int(seed)
477
+
478
+ input_image = _resize_to_divisible(input_image.convert("RGB"))
479
+ W, H = input_image.size
480
+
481
+ yield (
482
+ gr.update(visible=True, value=None, label="Running FLUX.2-Klein…"),
483
+ gr.update(visible=False, value=None),
484
+ gr.update(value=seed),
485
+ )
486
+
487
+ gen_torch = torch.Generator(device="cuda").manual_seed(seed)
488
+ with torch.no_grad():
489
+ klein_out = klein_pipe(
490
+ prompt=prompt,
491
+ image=input_image,
492
+ num_inference_steps=int(steps),
493
+ guidance_scale=float(guidance_scale),
494
+ generator=gen_torch,
495
+ output_type="pil",
496
+ )
497
+ klein_img: Image.Image = klein_out.images[0]
498
+
499
+ if klein_img.size != (W, H):
500
+ klein_img = klein_img.resize((W, H), Image.LANCZOS)
501
+
502
+ yield (
503
+ gr.update(visible=True, value=klein_img, label="FLUX.2-Klein done β€” encoding for PiD…"),
504
+ gr.update(visible=False),
505
+ gr.update(),
506
+ )
507
+
508
+ torch.cuda.empty_cache()
509
+
510
+ klein_arr = np.array(klein_img).astype(np.float32) / 255.0
511
+ klein_tensor_01 = torch.from_numpy(klein_arr).permute(2, 0, 1).unsqueeze(0)
512
+
513
+ final_latent = _encode_image_to_latent(klein_tensor_01)
514
+ baseline_01 = klein_tensor_01.to(dtype=DTYPE, device="cuda")
515
+ final_sigma = float(pipeline.scheduler.sigmas[-1].item())
516
+
517
+ pid_model = _pick_pid_model(max(H, W))
518
+ pid_img = None
519
+
520
+ for k, total, x in _pid_stream(
521
+ pid_model, final_latent, baseline_01, final_sigma, prompt,
522
+ num_steps=PID_INFERENCE_STEPS,
523
+ ):
524
+ pid_img = _pid_pixel_to_pil(x)
525
+ yield (
526
+ gr.update(visible=True, value=pid_img, label=f"Upscaling with PiD β€” step {k}/{total}"),
527
+ gr.update(visible=False),
528
+ gr.update(),
529
+ )
530
+
531
+ yield (
532
+ gr.update(visible=False, value=None),
533
+ gr.update(visible=True, value=(klein_img, pid_img)),
534
+ gr.update(),
535
+ )
536
+
537
+
538
+ @spaces.GPU(duration=90, size="xlarge")
539
+ def i2i_generate(*args, **kwargs):
540
+ yield from _i2i_generate_core(*args, **kwargs)
541
+
542
+
543
+ UPSCALER_MAX_SIDE = 1024
544
+
545
+
546
+ def _upscaler_dim_info(image: Image.Image):
547
+ if image is None:
548
+ return "_Upload an image to see its upscale dimensions._"
549
+ w, h = image.size
550
+ scale = min(UPSCALER_MAX_SIDE / w, UPSCALER_MAX_SIDE / h, 1.0)
551
+ nw = max(16, (int(w * scale) // 16) * 16)
552
+ nh = max(16, (int(h * scale) // 16) * 16)
553
+ out_w, out_h = nw * SR_SCALE, nh * SR_SCALE
554
+ return (
555
+ f"**Input:** {w} Γ— {h} px β†’ "
556
+ f"**Processed:** {nw} Γ— {nh} px β†’ "
557
+ f"**Upscaled output:** {out_w} Γ— {out_h} px "
558
+ f"*({SR_SCALE}Γ— via PiD)*"
559
+ )
560
+
561
+
562
+ def _upscaler_core(input_image: Image.Image, prompt: str):
563
+ if input_image is None:
564
+ raise gr.Error("Please upload an image to upscale.")
565
+
566
+ caption = prompt.strip() if prompt and prompt.strip() else "high quality, detailed, sharp"
567
+
568
+ img_rgb = input_image.convert("RGB")
569
+ w, h = img_rgb.size
570
+ scale = min(UPSCALER_MAX_SIDE / w, UPSCALER_MAX_SIDE / h, 1.0)
571
+ nw = max(16, (int(w * scale) // 16) * 16)
572
+ nh = max(16, (int(h * scale) // 16) * 16)
573
+ if (nw, nh) != (w, h):
574
+ img_rgb = img_rgb.resize((nw, nh), Image.LANCZOS)
575
+
576
+ input_pil = img_rgb
577
+
578
+ yield (
579
+ gr.update(visible=True, value=input_pil, label="Encoding image…"),
580
+ gr.update(visible=False, value=None),
581
+ )
582
+
583
+ arr_01 = np.array(img_rgb).astype(np.float32) / 255.0
584
+ tensor_01 = torch.from_numpy(arr_01).permute(2, 0, 1).unsqueeze(0)
585
+
586
+ latent = _encode_image_to_latent(tensor_01)
587
+ baseline_01 = tensor_01.to(dtype=DTYPE, device="cuda")
588
+ sigma = float(pipeline.scheduler.sigmas[-1].item())
589
+
590
+ torch.cuda.empty_cache()
591
+
592
+ pid_model = _pick_pid_model(max(nw, nh))
593
+ pid_img = None
594
+
595
+ for k, total, x in _pid_stream(
596
+ pid_model, latent, baseline_01, sigma, caption,
597
+ num_steps=PID_INFERENCE_STEPS,
598
+ ):
599
+ pid_img = _pid_pixel_to_pil(x)
600
+ yield (
601
+ gr.update(visible=True, value=pid_img, label=f"Upscaling with PiD β€” step {k}/{total}"),
602
+ gr.update(visible=False),
603
+ )
604
+
605
+ yield (
606
+ gr.update(visible=False, value=None),
607
+ gr.update(visible=True, value=(input_pil, pid_img)),
608
+ )
609
+
610
+
611
+ @spaces.GPU(duration=90, size="xlarge")
612
+ def upscaler_run(*args, **kwargs):
613
+ yield from _upscaler_core(*args, **kwargs)
614
+
615
+ DESCRIPTION = """
616
+ ## PiD β€” Pixel Diffusion Decoder
617
+
618
+ **Text2Image** β€” [Z-Image](https://huggingface.co/Tongyi-MAI/Z-Image) with live TAEF1 previews β†’
619
+ [PiD](https://huggingface.co/nvidia/PiD) 4-step pixel-diffusion 4Γ— SR Β·
620
+ **Image2Image** β€” FLUX.2-Klein β†’ PiD 4Γ— Β·
621
+ **Upscaler** β€” PiD direct 4Γ— Β·
622
+ [GitHub β†—](https://github.com/PRITHIVSAKTHIUR/PiD-Image-Upscaler)
623
+ """
624
+
625
+ with gr.Blocks(theme=orange_red_theme) as demo:
626
+
627
+ gr.Markdown(DESCRIPTION)
628
+
629
+ with gr.Tabs():
630
+
631
+ # ── Tab 1 : Image2Image ──────────────────────────────────────────
632
+ with gr.Tab("Image2Image PiD"):
633
+
634
+ gr.Markdown(
635
+ "Upload any image β€” "
636
+ "**[FLUX.2-Klein](https://huggingface.co/black-forest-labs/FLUX.2-klein-4B)** "
637
+ "refines it then **PiD** super-resolves the result **4Γ—**. \n"
638
+ "The slider compares the Klein output **(left)** to the PiD upscale **(right)**."
639
+ )
640
+
641
+ with gr.Row():
642
+ with gr.Column(scale=1, min_width=280):
643
+ i2i_input = gr.Image(label="Input image", type="pil", height=340)
644
+ i2i_dim_info = gr.Markdown(
645
+ "_Upload an image to see its processed dimensions._"
646
+ )
647
+ i2i_prompt = gr.Textbox(
648
+ label="Prompt / description",
649
+ placeholder="Describe the image content or the desired style…",
650
+ lines=3,
651
+ )
652
+ i2i_run = gr.Button("Run Image2Image", variant="primary", size="lg")
653
+
654
+ with gr.Accordion("Advanced Settings", open=False):
655
+ i2i_seed = gr.Slider(
656
+ label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0
657
+ )
658
+ i2i_rand = gr.Checkbox(label="Randomize seed", value=True)
659
+ i2i_guidance = gr.Slider(
660
+ label="Guidance Scale",
661
+ minimum=0.0, maximum=10.0, step=0.1, value=1.0,
662
+ )
663
+ i2i_steps = gr.Slider(
664
+ label="Steps", minimum=1, maximum=50, value=4, step=1
665
+ )
666
+
667
+ with gr.Column(scale=2, min_width=340):
668
+ i2i_live = gr.Image(
669
+ label="Output preview",
670
+ visible=True, show_label=True,
671
+ type="pil", height=380,
672
+ )
673
+ i2i_slider = gr.ImageSlider(
674
+ label="FLUX.2-Klein (left) ↔ PiD 4Γ— upscale (right)",
675
+ visible=False,
676
+ type="pil",
677
+ height=680,
678
+ max_height=680,
679
+ )
680
+
681
+ i2i_input.upload(
682
+ fn=update_dimensions_on_upload,
683
+ inputs=i2i_input,
684
+ outputs=i2i_dim_info,
685
+ )
686
+ i2i_run.click(
687
+ fn=i2i_generate,
688
+ inputs=[i2i_input, i2i_prompt, i2i_seed, i2i_rand, i2i_guidance, i2i_steps],
689
+ outputs=[i2i_live, i2i_slider, i2i_seed],
690
+ )
691
+
692
+ # ── Tab 2 : Text2Image ───────────────────────────────────────────
693
+ with gr.Tab("Text2Image PiD"):
694
+
695
+ with gr.Row():
696
+ prompt = gr.Textbox(
697
+ show_label=False,
698
+ placeholder="Describe what you want to generate…",
699
+ value=(
700
+ "A photorealistic Labrador retriever resting beside a campfire at night, "
701
+ "glowing warm firelight reflecting on detailed fur, cinematic outdoor atmosphere."
702
+ ),
703
+ max_lines=1,
704
+ scale=4,
705
+ container=False,
706
+ )
707
+ run = gr.Button("Generate", variant="primary", scale=1)
708
+
709
+ live_preview = gr.Image(
710
+ label="Z-Image with PiD",
711
+ visible=True, show_label=True,
712
+ type="pil", height=680,
713
+ )
714
+ slider = gr.ImageSlider(
715
+ label="Z-Image (left) ↔ PiD 4Γ— upscale (right)",
716
+ visible=False,
717
+ type="pil",
718
+ height=680,
719
+ max_height=680,
720
+ )
721
+
722
+ with gr.Accordion("Advanced settings", open=False):
723
+ with gr.Row():
724
+ resolution = gr.Radio(
725
+ label="Z-Image resolution",
726
+ choices=[512, 1024],
727
+ value=512,
728
+ info="512 β†’ 2048Β² (PiD 2k) Β· 1024 β†’ 4096Β² (PiD 2kto4k_v1pt5)",
729
+ )
730
+ num_inference_steps = gr.Slider(
731
+ label="Z-Image steps",
732
+ minimum=8, maximum=50, step=1, value=28,
733
+ )
734
+ with gr.Row():
735
+ guidance_scale = gr.Slider(
736
+ label="Guidance",
737
+ minimum=1.0, maximum=10.0, step=0.5, value=5.0,
738
+ )
739
+ seed = gr.Number(label="Seed", value=0, precision=0)
740
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
741
+
742
+ run.click(
743
+ fn=generate,
744
+ inputs=[prompt, num_inference_steps, guidance_scale, seed, resolution, randomize_seed],
745
+ outputs=[live_preview, slider, seed],
746
+ )
747
+
748
+ # ── Tab 3 : Upscaler ─────────────────────────────────────────────
749
+ with gr.Tab("Image Upscaler"):
750
+
751
+ gr.Markdown(
752
+ "Upload any image and **PiD** will upscale it **4Γ—** directly β€” "
753
+ "no text generation step needed. \n"
754
+ "An optional prompt / description helps PiD produce sharper, "
755
+ "more faithful detail. \n"
756
+ "The slider compares the **original** *(left)* to the **PiD 4Γ— upscale** *(right)*."
757
+ )
758
+
759
+ with gr.Row():
760
+ with gr.Column(scale=1, min_width=280):
761
+ up_input = gr.Image(
762
+ label="Image to upscale",
763
+ type="pil", height=360,
764
+ )
765
+ up_dim_info = gr.Markdown(
766
+ "_Upload an image to see its upscale dimensions._"
767
+ )
768
+ up_prompt = gr.Textbox(
769
+ label="Optional prompt / description",
770
+ placeholder="Describe the image for better detail (leave blank for auto)…",
771
+ lines=3,
772
+ visible=False,
773
+ )
774
+ up_run = gr.Button("Upscale 4x", variant="primary", size="lg")
775
+
776
+ with gr.Column(scale=2, min_width=340):
777
+ up_live = gr.Image(
778
+ label="Output preview",
779
+ visible=True, show_label=True,
780
+ type="pil", height=380,
781
+ )
782
+ up_slider = gr.ImageSlider(
783
+ label="Original (left) ↔ PiD 4Γ— upscale (right)",
784
+ visible=False,
785
+ type="pil",
786
+ height=680,
787
+ max_height=680,
788
+ )
789
+
790
+ up_input.upload(
791
+ fn=_upscaler_dim_info,
792
+ inputs=up_input,
793
+ outputs=up_dim_info,
794
+ )
795
+ up_run.click(
796
+ fn=upscaler_run,
797
+ inputs=[up_input, up_prompt],
798
+ outputs=[up_live, up_slider],
799
+ )
800
+
801
+ if __name__ == "__main__":
802
+ demo.queue().launch(mcp_server=True, ssr_mode=False, show_error=True)
requirements.txt ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --extra-index-url https://download.pytorch.org/whl/cu128
2
+
3
+ git+https://github.com/huggingface/transformers.git@v4.57.6
4
+ accelerate
5
+ diffusers
6
+ peft
7
+ opencv-python-headless
8
+ huggingface_hub
9
+ sentencepiece
10
+ termcolor
11
+ loguru
12
+ omegaconf
13
+ kernels
14
+ hydra-core
15
+ spaces
16
+ einops
17
+ fvcore
18
+ gradio==5.49.1
19
+ hf_xet
20
+ torch==2.10.0
21
+ torchvision==0.25.0
22
+ matplotlib==3.10.9
23
+ numpy==1.26.4
24
+ pandas==2.2.3
25
+ iopath
26
+ imageio
27
+ boto3
28
+ botocore
29
+ pyyaml
30
+ av
31
+ wandb