OzzyGT HF Staff commited on
Commit
c310bd2
·
1 Parent(s): cd7b58f

model selector, better offline UX

Browse files
Files changed (2) hide show
  1. .gitignore +44 -0
  2. app.py +85 -24
.gitignore ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ .eggs/
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+ env/
16
+ ENV/
17
+
18
+ # Environment / secrets
19
+ .env
20
+ .env.*
21
+
22
+ # Caches
23
+ .cache/
24
+ .ruff_cache/
25
+ .mypy_cache/
26
+ .pytest_cache/
27
+
28
+ # Hugging Face / model caches
29
+ .huggingface/
30
+ hf_cache/
31
+
32
+ # Jupyter
33
+ .ipynb_checkpoints/
34
+
35
+ # OS / editor
36
+ .DS_Store
37
+ Thumbs.db
38
+ .idea/
39
+ .vscode/
40
+ *.swp
41
+
42
+ # Gradio
43
+ .gradio/
44
+ flagged/
app.py CHANGED
@@ -111,10 +111,6 @@ def _load(model_id):
111
 
112
  def get_model(precision):
113
  """Return (model, processor); load the selected model, swapping out any previously-loaded one."""
114
- if IS_ZERO_GPU:
115
- precision = (
116
- DEFAULT_PRECISION # ZeroGPU serves bf16 only, whatever the caller passes
117
- )
118
  model_id = MODELS[precision]
119
  if _current["id"] != model_id:
120
  # Free the current model before loading the new one (one resident at a time).
@@ -128,8 +124,30 @@ def get_model(precision):
128
  return _current["model"], _current["processor"]
129
 
130
 
131
- # Preload the default so the first caption is fast (and gives ZeroGPU its module-scope load).
132
- get_model(DEFAULT_PRECISION)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
 
135
  def _extract_json(text: str) -> str:
@@ -156,6 +174,8 @@ def caption(
156
  if image is None:
157
  raise gr.Error("An image is required.")
158
 
 
 
159
  model, processor = get_model(precision)
160
  messages = [
161
  {
@@ -188,27 +208,63 @@ def caption(
188
  return _extract_json(text)
189
 
190
 
191
- with gr.Blocks(title="Ideogram4 Caption (Gemma4)") as demo:
192
- gr.Markdown(
193
- "# Ideogram4 Caption Gemma4\n"
194
- "Upload an image get Ideogram4's rich JSON caption (with pixel-space `bbox`es). Edit the JSON "
195
- "(add / remove / move / resize elements) and feed it to the Ideogram4 generation Space."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  )
 
 
 
197
  with gr.Row():
198
  with gr.Column():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  image = gr.Image(label="Source image", type="pil")
200
- # Hidden on ZeroGPU (bf16-only); locally the user picks one model at a time (switching reloads).
201
- precision = gr.Dropdown(
202
- choices=list(MODELS),
203
- value=DEFAULT_PRECISION,
204
- label="Model / precision",
205
- info="One model at a time — switching reloads. bf16 (full) needs a >=24GB GPU; "
206
- "use SDNQ 4-bit/8-bit on a 16GB card.",
207
- visible=not IS_ZERO_GPU,
208
- )
209
- instruction = gr.Textbox(
210
- label="Instruction", value=DEFAULT_INSTRUCTION, lines=10
211
- )
212
  with gr.Row():
213
  max_new_tokens = gr.Slider(
214
  256, 4096, value=2048, step=64, label="Max new tokens"
@@ -216,10 +272,15 @@ with gr.Blocks(title="Ideogram4 Caption (Gemma4)") as demo:
216
  temperature = gr.Slider(
217
  0.0, 2.0, value=0.0, step=0.1, label="Temperature (0 = greedy)"
218
  )
219
- run = gr.Button("Caption", variant="primary")
220
  with gr.Column():
221
  result = gr.Code(label="Ideogram4 JSON caption", language="json")
222
 
 
 
 
 
 
 
223
  run.click(
224
  caption,
225
  inputs=[image, instruction, precision, max_new_tokens, temperature],
 
111
 
112
  def get_model(precision):
113
  """Return (model, processor); load the selected model, swapping out any previously-loaded one."""
 
 
 
 
114
  model_id = MODELS[precision]
115
  if _current["id"] != model_id:
116
  # Free the current model before loading the new one (one resident at a time).
 
124
  return _current["model"], _current["processor"]
125
 
126
 
127
+ # Two paths:
128
+ # ZeroGPU -> the platform hands out the GPU only for the duration of a @spaces.GPU call and takes
129
+ # it right back, so there's nothing to "hog". We just preload bf16 at module scope (the
130
+ # canonical ZeroGPU pattern) and skip the whole load/unload + quantization machinery.
131
+ # Local -> we hold a real GPU, so this support app must not squat on VRAM while the user runs the
132
+ # Ideogram generation app. Nothing loads until the Load button; Unload frees it again.
133
+ if IS_ZERO_GPU:
134
+ get_model(DEFAULT_PRECISION)
135
+
136
+
137
+ @spaces.GPU(duration=120)
138
+ def load_model(precision):
139
+ """Load the selected model into VRAM (local only; nothing is resident until this runs)."""
140
+ get_model(precision)
141
+ return f"✅ Loaded: {precision}"
142
+
143
+
144
+ def unload_model():
145
+ """Evict the resident model from VRAM and RAM so it stops hogging the GPU (local only)."""
146
+ _current.update(id=None, model=None, processor=None)
147
+ gc.collect()
148
+ if torch.cuda.is_available():
149
+ torch.cuda.empty_cache()
150
+ return "⚪ No model loaded"
151
 
152
 
153
  def _extract_json(text: str) -> str:
 
174
  if image is None:
175
  raise gr.Error("An image is required.")
176
 
177
+ if IS_ZERO_GPU:
178
+ precision = DEFAULT_PRECISION # ZeroGPU serves bf16 only
179
  model, processor = get_model(precision)
180
  messages = [
181
  {
 
208
  return _extract_json(text)
209
 
210
 
211
+ CSS = """
212
+ #caption-btn {
213
+ background: linear-gradient(180deg, #2563eb 0%, #1e3a8a 100%) !important;
214
+ color: #fff !important; border: none !important;
215
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.15), 0 1px 2px rgba(0,0,0,0.25) !important;
216
+ }
217
+ #load-btn {
218
+ background: linear-gradient(180deg, #15803d 0%, #14532d 100%) !important;
219
+ color: #fff !important; border: none !important;
220
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.15), 0 1px 2px rgba(0,0,0,0.25) !important;
221
+ }
222
+ #unload-btn {
223
+ background: linear-gradient(180deg, #b91c1c 0%, #7f1d1d 100%) !important;
224
+ color: #fff !important; border: none !important;
225
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.15), 0 1px 2px rgba(0,0,0,0.25) !important;
226
+ }
227
+ #caption-btn, #load-btn, #unload-btn { min-height: 56px !important; height: 56px !important; }
228
+ """
229
+
230
+ intro = (
231
+ "# Ideogram4 Caption — Gemma4\n"
232
+ "Upload an image → get Ideogram4's rich JSON caption (with pixel-space `bbox`es). Edit the JSON "
233
+ "(add / remove / move / resize elements) and feed it to the Ideogram4 generation Space."
234
+ )
235
+ if not IS_ZERO_GPU:
236
+ intro += (
237
+ "\n\nOne model at a time — switching + Load reloads. bf16 needs a >=24GB GPU; "
238
+ "SDNQ 4/8-bit fit 16GB."
239
  )
240
+
241
+ with gr.Blocks(title="Ideogram4 Caption (Gemma4)", css=CSS) as demo:
242
+ gr.Markdown(intro)
243
  with gr.Row():
244
  with gr.Column():
245
+ run = gr.Button("Caption", variant="primary", elem_id="caption-btn")
246
+
247
+ if IS_ZERO_GPU:
248
+ # ZeroGPU: bf16 is preloaded and the GPU is per-request — no selector / load / unload.
249
+ precision = gr.State(value=DEFAULT_PRECISION)
250
+ else:
251
+ # Local: pick a model, load it on demand, and free it so we don't squat on VRAM.
252
+ with gr.Row(equal_height=True):
253
+ precision = gr.Dropdown(
254
+ choices=list(MODELS),
255
+ value=DEFAULT_PRECISION,
256
+ show_label=False,
257
+ scale=2,
258
+ )
259
+ load_btn = gr.Button("Load model", elem_id="load-btn")
260
+ unload_btn = gr.Button("Unload model", elem_id="unload-btn")
261
+ model_status = gr.Textbox(
262
+ label="Model status", value="⚪ No model loaded", interactive=False
263
+ )
264
+
265
  image = gr.Image(label="Source image", type="pil")
266
+ # The Ideogram4 schema prompt is fixed and hidden; kept as a state so it's still passed to caption().
267
+ instruction = gr.State(value=DEFAULT_INSTRUCTION)
 
 
 
 
 
 
 
 
 
 
268
  with gr.Row():
269
  max_new_tokens = gr.Slider(
270
  256, 4096, value=2048, step=64, label="Max new tokens"
 
272
  temperature = gr.Slider(
273
  0.0, 2.0, value=0.0, step=0.1, label="Temperature (0 = greedy)"
274
  )
 
275
  with gr.Column():
276
  result = gr.Code(label="Ideogram4 JSON caption", language="json")
277
 
278
+ if not IS_ZERO_GPU:
279
+ load_btn.click(load_model, inputs=precision, outputs=model_status, api_name="load")
280
+ unload_btn.click(
281
+ unload_model, inputs=None, outputs=model_status, api_name="unload"
282
+ )
283
+
284
  run.click(
285
  caption,
286
  inputs=[image, instruction, precision, max_new_tokens, temperature],