OrlandoHugBot commited on
Commit
fd25dcd
·
verified ·
1 Parent(s): e28e511

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +172 -130
app.py CHANGED
@@ -1,28 +1,40 @@
1
  """
2
- UniPic-3 DMD – ZeroGPU friendly demo
3
- - Pre-cache all weights on CPU
4
- - GPU phase does ZERO network IO
5
- - SSR disabled
6
  """
7
 
8
  import os
9
  import sys
 
 
10
  import torch
11
  import gradio as gr
12
  from PIL import Image
13
- from spaces import GPU
14
  from huggingface_hub import snapshot_download
 
 
 
 
 
15
 
16
- # -----------------------------------------------------------------------------
17
- # Paths
18
- # -----------------------------------------------------------------------------
19
  MODEL_ID = "Skywork/Unipic3-DMD"
20
  CACHE_ROOT = "./hf_cache"
21
  LOCAL_MODEL_DIR = os.path.join(CACHE_ROOT, MODEL_ID)
22
 
23
- # -----------------------------------------------------------------------------
24
- # Pre-cache weights (CPU ONLY)
25
- # -----------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
26
  def precache_weights():
27
  if os.path.exists(LOCAL_MODEL_DIR):
28
  print("✅ Weights already cached")
@@ -47,10 +59,10 @@ def precache_weights():
47
 
48
  print("✅ Pre-cache complete")
49
 
50
-
51
- # -----------------------------------------------------------------------------
52
  # Local imports AFTER cache
53
- # -----------------------------------------------------------------------------
 
54
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
55
 
56
  from diffusers import (
@@ -65,152 +77,182 @@ try:
65
  except ImportError:
66
  from diffusers import QwenImageEditPipeline
67
 
68
- # -----------------------------------------------------------------------------
69
- # Globals
70
- # -----------------------------------------------------------------------------
71
- pipe = None
72
 
73
- # -----------------------------------------------------------------------------
74
- # Load model (GPU stage, NO DOWNLOAD)
75
- # -----------------------------------------------------------------------------
76
- def load_model():
77
  global pipe
78
 
79
- if pipe is not None:
80
- return
 
81
 
82
- if not torch.cuda.is_available():
83
- raise RuntimeError(" GPU not available")
84
 
85
- device = torch.device("cuda")
86
- dtype = torch.bfloat16
87
 
88
- print("🚀 Loading UniPic-3 DMD from local cache")
89
- print("Device:", device)
 
90
 
91
- scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
92
- LOCAL_MODEL_DIR, subfolder="scheduler"
93
- )
 
 
94
 
95
- text_encoder = AutoModel.from_pretrained(
96
- LOCAL_MODEL_DIR,
97
- subfolder="text_encoder",
98
- torch_dtype=dtype,
99
- ).to(device)
100
 
101
- tokenizer = AutoTokenizer.from_pretrained(
102
- LOCAL_MODEL_DIR, subfolder="tokenizer"
103
- )
104
 
105
- processor = Qwen2VLProcessor.from_pretrained(
106
- LOCAL_MODEL_DIR, subfolder="processor"
107
- )
 
 
108
 
109
- transformer = QwenImageTransformer2DModel.from_pretrained(
110
- LOCAL_MODEL_DIR,
111
- subfolder="ema_transformer",
112
- torch_dtype=dtype,
113
- ).to(device)
114
-
115
- vae = AutoencoderKLQwenImage.from_pretrained(
116
- LOCAL_MODEL_DIR,
117
- subfolder="vae",
118
- torch_dtype=dtype,
119
- ).to(device)
120
-
121
- pipe = QwenImageEditPipeline(
122
- scheduler=scheduler,
123
- vae=vae,
124
- text_encoder=text_encoder,
125
- tokenizer=tokenizer,
126
- processor=processor,
127
- transformer=transformer,
128
- )
129
 
130
- pipe.to(device)
131
 
132
- print(" Model loaded successfully")
 
133
 
 
 
 
134
 
135
- # -----------------------------------------------------------------------------
136
- # Inference
137
- # -----------------------------------------------------------------------------
138
- def run(
139
  img1, img2, img3, img4, img5, img6,
140
  prompt, cfg, seed, steps
141
  ):
142
  global pipe
143
 
144
- if pipe is None:
145
- load_model()
146
-
147
- images = [i for i in [img1, img2, img3, img4, img5, img6] if i is not None]
148
- if not images:
149
- return None, "❌ Please upload at least one image"
150
-
151
- images = [img.convert("RGB") for img in images]
152
-
153
- gen = torch.Generator(device="cuda").manual_seed(int(seed))
154
-
155
- with torch.no_grad():
156
- if len(images) == 1:
157
- out = pipe(
158
- images[0],
159
- prompt=prompt,
160
- height=768,
161
- width=768,
162
- num_inference_steps=steps,
163
- true_cfg_scale=cfg,
164
- generator=gen,
165
- ).images[0]
166
- else:
167
- out = pipe(
168
- images=images,
169
- prompt=prompt,
170
- height=768,
171
- width=768,
172
- num_inference_steps=steps,
173
- true_cfg_scale=cfg,
174
- generator=gen,
175
- ).images[0]
176
-
177
- return out, "✅ Done"
178
-
179
-
180
- # -----------------------------------------------------------------------------
181
- # UI
182
- # -----------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  with gr.Blocks(title="UniPic-3 DMD (ZeroGPU)") as demo:
184
- gr.Markdown("# 🔥 UniPic-3 DMD (ZeroGPU + Precached)")
185
 
186
- imgs = [gr.Image(type="pil", label=f"Image {i+1}") for i in range(6)]
187
- prompt = gr.Textbox(label="Prompt", value="Combine the reference images.")
188
- cfg = gr.Slider(1, 8, value=4)
189
- seed = gr.Number(42)
190
- steps = gr.Slider(1, 8, value=6)
191
 
192
- btn = gr.Button("Generate")
193
- out = gr.Image()
194
- status = gr.Textbox()
 
 
 
 
 
 
 
 
 
 
195
 
196
  btn.click(
197
- run,
198
  inputs=[*imgs, prompt, cfg, seed, steps],
199
- outputs=[out, status],
200
  )
201
 
 
 
 
 
 
 
 
202
 
203
- # -----------------------------------------------------------------------------
204
  # Entry
205
- # -----------------------------------------------------------------------------
206
- @GPU
207
- def main():
208
- # CPU stage (no GPU time)
209
  precache_weights()
210
 
211
- # Start Gradio (NO SSR)
212
  demo.launch(ssr_mode=False)
213
-
214
-
215
- if __name__ == "__main__":
216
- main()
 
1
  """
2
+ UniPic-3 DMD – ZeroGPU Final Architecture
3
+ UI Always-On (CPU) + GPU On-Demand Inference
4
+ with GPU Queue Status Indicator
 
5
  """
6
 
7
  import os
8
  import sys
9
+ import time
10
+ import threading
11
  import torch
12
  import gradio as gr
13
  from PIL import Image
 
14
  from huggingface_hub import snapshot_download
15
+ from spaces import GPU
16
+
17
+ # =============================================================================
18
+ # Paths & Globals
19
+ # =============================================================================
20
 
 
 
 
21
  MODEL_ID = "Skywork/Unipic3-DMD"
22
  CACHE_ROOT = "./hf_cache"
23
  LOCAL_MODEL_DIR = os.path.join(CACHE_ROOT, MODEL_ID)
24
 
25
+ pipe = None
26
+ model_lock = threading.Lock()
27
+
28
+ # GPU state (for UI display)
29
+ GPU_STATE = {
30
+ "status": "idle", # idle | waiting | loading | ready | running | error
31
+ "message": "UI ready. GPU not requested yet."
32
+ }
33
+
34
+ # =============================================================================
35
+ # CPU Stage: Pre-cache weights (NO GPU)
36
+ # =============================================================================
37
+
38
  def precache_weights():
39
  if os.path.exists(LOCAL_MODEL_DIR):
40
  print("✅ Weights already cached")
 
59
 
60
  print("✅ Pre-cache complete")
61
 
62
+ # =============================================================================
 
63
  # Local imports AFTER cache
64
+ # =============================================================================
65
+
66
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
67
 
68
  from diffusers import (
 
77
  except ImportError:
78
  from diffusers import QwenImageEditPipeline
79
 
80
+ # =============================================================================
81
+ # GPU Stage: Model loader (NO network)
82
+ # =============================================================================
 
83
 
84
+ def load_model_on_gpu():
 
 
 
85
  global pipe
86
 
87
+ with model_lock:
88
+ if pipe is not None:
89
+ return
90
 
91
+ GPU_STATE["status"] = "loading"
92
+ GPU_STATE["message"] = "Loading model onto GPU..."
93
 
94
+ device = torch.device("cuda")
95
+ dtype = torch.bfloat16
96
 
97
+ scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
98
+ LOCAL_MODEL_DIR, subfolder="scheduler"
99
+ )
100
 
101
+ text_encoder = AutoModel.from_pretrained(
102
+ LOCAL_MODEL_DIR,
103
+ subfolder="text_encoder",
104
+ torch_dtype=dtype,
105
+ ).to(device)
106
 
107
+ tokenizer = AutoTokenizer.from_pretrained(
108
+ LOCAL_MODEL_DIR, subfolder="tokenizer"
109
+ )
 
 
110
 
111
+ processor = Qwen2VLProcessor.from_pretrained(
112
+ LOCAL_MODEL_DIR, subfolder="processor"
113
+ )
114
 
115
+ transformer = QwenImageTransformer2DModel.from_pretrained(
116
+ LOCAL_MODEL_DIR,
117
+ subfolder="ema_transformer",
118
+ torch_dtype=dtype,
119
+ ).to(device)
120
 
121
+ vae = AutoencoderKLQwenImage.from_pretrained(
122
+ LOCAL_MODEL_DIR,
123
+ subfolder="vae",
124
+ torch_dtype=dtype,
125
+ ).to(device)
126
+
127
+ pipe = QwenImageEditPipeline(
128
+ scheduler=scheduler,
129
+ vae=vae,
130
+ text_encoder=text_encoder,
131
+ tokenizer=tokenizer,
132
+ processor=processor,
133
+ transformer=transformer,
134
+ )
 
 
 
 
 
 
135
 
136
+ pipe.to(device)
137
 
138
+ GPU_STATE["status"] = "ready"
139
+ GPU_STATE["message"] = "GPU ready. Model loaded."
140
 
141
+ # =============================================================================
142
+ # GPU On-Demand Inference (THIS is the only @GPU function)
143
+ # =============================================================================
144
 
145
+ @GPU
146
+ def run_inference(
 
 
147
  img1, img2, img3, img4, img5, img6,
148
  prompt, cfg, seed, steps
149
  ):
150
  global pipe
151
 
152
+ try:
153
+ GPU_STATE["status"] = "waiting"
154
+ GPU_STATE["message"] = "Waiting for GPU..."
155
+
156
+ # ZeroGPU will block here until GPU is assigned
157
+ if not torch.cuda.is_available():
158
+ return None, "⏳ Waiting for GPU, please retry."
159
+
160
+ load_model_on_gpu()
161
+
162
+ GPU_STATE["status"] = "running"
163
+ GPU_STATE["message"] = "Running inference..."
164
+
165
+ images = [i for i in [img1, img2, img3, img4, img5, img6] if i is not None]
166
+ if not images:
167
+ return None, "❌ Please upload at least one image."
168
+
169
+ images = [img.convert("RGB") for img in images]
170
+
171
+ generator = torch.Generator(device="cuda").manual_seed(int(seed))
172
+
173
+ with torch.no_grad():
174
+ if len(images) == 1:
175
+ out = pipe(
176
+ images[0],
177
+ prompt=prompt,
178
+ height=768,
179
+ width=768,
180
+ num_inference_steps=steps,
181
+ true_cfg_scale=cfg,
182
+ generator=generator,
183
+ ).images[0]
184
+ else:
185
+ out = pipe(
186
+ images=images,
187
+ prompt=prompt,
188
+ height=768,
189
+ width=768,
190
+ num_inference_steps=steps,
191
+ true_cfg_scale=cfg,
192
+ generator=generator,
193
+ ).images[0]
194
+
195
+ GPU_STATE["status"] = "ready"
196
+ GPU_STATE["message"] = "Inference complete."
197
+
198
+ return out, "✅ Done"
199
+
200
+ except Exception as e:
201
+ GPU_STATE["status"] = "error"
202
+ GPU_STATE["message"] = str(e)
203
+ return None, f"❌ Error: {e}"
204
+
205
+ # =============================================================================
206
+ # UI Helpers (CPU)
207
+ # =============================================================================
208
+
209
+ def get_gpu_status():
210
+ return f"**GPU Status:** `{GPU_STATE['status']}`\n\n{GPU_STATE['message']}"
211
+
212
+ # =============================================================================
213
+ # UI (ALWAYS CPU, ALWAYS ON)
214
+ # =============================================================================
215
+
216
  with gr.Blocks(title="UniPic-3 DMD (ZeroGPU)") as demo:
217
+ gr.Markdown("# 🔥 UniPic-3 DMD ZeroGPU Demo")
218
 
219
+ status_box = gr.Markdown(get_gpu_status())
 
 
 
 
220
 
221
+ with gr.Row():
222
+ with gr.Column():
223
+ imgs = [gr.Image(type="pil", label=f"Image {i+1}") for i in range(6)]
224
+ prompt = gr.Textbox(label="Prompt", value="Combine the reference images.")
225
+ cfg = gr.Slider(1, 8, value=4, label="CFG")
226
+ seed = gr.Number(42, precision=0, label="Seed")
227
+ steps = gr.Slider(1, 6, value=6, label="Steps")
228
+
229
+ btn = gr.Button("🚀 Generate")
230
+
231
+ with gr.Column():
232
+ out = gr.Image(label="Output")
233
+ msg = gr.Textbox(label="Result")
234
 
235
  btn.click(
236
+ run_inference,
237
  inputs=[*imgs, prompt, cfg, seed, steps],
238
+ outputs=[out, msg],
239
  )
240
 
241
+ # Periodic status refresh (CPU only)
242
+ demo.load(
243
+ fn=get_gpu_status,
244
+ inputs=[],
245
+ outputs=status_box,
246
+ every=1.0,
247
+ )
248
 
249
+ # =============================================================================
250
  # Entry
251
+ # =============================================================================
252
+
253
+ if __name__ == "__main__":
254
+ # CPU phase: cache weights
255
  precache_weights()
256
 
257
+ # UI always-on
258
  demo.launch(ssr_mode=False)