hmb HF Staff commited on
Commit
4816ffd
·
verified ·
1 Parent(s): 64f4129

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -12
app.py CHANGED
@@ -34,8 +34,64 @@ def _save_image(image) -> dict:
34
  }
35
 
36
 
37
- @spaces.GPU
38
- def generate_image(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  prompt: str,
40
  height: int,
41
  width: int,
@@ -43,15 +99,9 @@ def generate_image(
43
  seed: int,
44
  randomize_seed: bool,
45
  ):
46
- """Generate an image from a prompt using Z-Image-Turbo on ZeroGPU.
47
-
48
- Bound to the workflow canvas as a `fn` operator node the workflow
49
- calls this Python function directly server-side, so the entire pipeline
50
- (frontend + ZeroGPU) lives in a single Space.
51
-
52
- Returns (image_dict, seed_used). The image is serialized to a /gradio_api
53
- file URL so JSON serialization across the fn bridge succeeds; the executor's
54
- `fromGradioOutput` turns the dict back into an image port value.
55
  """
56
  if not prompt or not prompt.strip():
57
  raise gr.Error("Please enter a prompt.")
@@ -72,7 +122,40 @@ def generate_image(
72
  return _save_image(image), int(seed)
73
 
74
 
75
- # The workflow (workflow.json) wires this function as a `fn` operator:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  # Prompt, Height, Width, Inference Steps, Seed, Randomize Seed ─▶
77
  # generate_image (fn operator, kind="fn") ─▶ Output Image, Seed Used
78
  #
 
34
  }
35
 
36
 
37
+ def _estimate_duration(prompt, height, width, num_inference_steps, seed, randomize_seed) -> int:
38
+ """Rough wall-clock estimate (seconds) for one Z-Image-Turbo call.
39
+
40
+ ZeroGPU's default per-call duration is 60s. Requesting less than you need
41
+ *raises* queue priority (shorter tasks get scheduled sooner) and — crucially
42
+ for a busy shared Space — frees the GPU slot for the next visitor far
43
+ sooner than holding it for a full minute, so far fewer users hit the
44
+ Space's "reached its GPU limit" rejection. Scaled by pixel count and steps;
45
+ clamped to a small floor/ceiling so a runaway slider can't starve the queue
46
+ or under-budget a big call.
47
+
48
+ Signature mirrors the GPU function exactly because @spaces.GPU passes the
49
+ decorated function's inputs straight through to the duration callable.
50
+
51
+ See: https://huggingface.co/docs/hub/en/spaces-zerogpu#duration-management
52
+ """
53
+ pixels = max(int(height), 1) * max(int(width), 1)
54
+ # ~0.4s/step at 1024^2, linear-ish in pixels. Big calls still need headroom.
55
+ per_step = 0.4 * (pixels / (1024 * 1024))
56
+ seconds = int(num_inference_steps) * per_step
57
+ return max(20, min(int(seconds) + 15, 120))
58
+
59
+
60
+ def _friendly_gpu_error(err: Exception) -> str:
61
+ """Turn ZeroGPU's terse allocator rejections into a clear, honest message.
62
+
63
+ 'Space app has reached its GPU limit' is a *Space-level* capacity rejection
64
+ (the shared ZeroGPU pool is saturated), not a per-user quota wall — it
65
+ reproduces regardless of inputs, account tier, or sign-in state. Don't make
66
+ an upgrade claim whose truth we can't pin down, so the message is neutral:
67
+ shared GPU at capacity, retry shortly.
68
+ """
69
+ msg = (str(err) or "").lower()
70
+ capacity_hints = (
71
+ "gpu limit", "reached its gpu limit", "gpu quota", "out of quota",
72
+ "quota", "no gpu", "could not allocate", "gpu is busy", "too many",
73
+ "concurrent",
74
+ )
75
+ if any(h in msg for h in capacity_hints):
76
+ return (
77
+ "⛔ This demo's shared GPU is at capacity right now — it's not a "
78
+ "problem with your prompt or your account. The GPU pool is fully "
79
+ "booked by other users at the moment. Please wait a minute and "
80
+ "retry; demand clears between bursts."
81
+ )
82
+ if "out of memory" in msg or "oom" in msg or "cuda" in msg:
83
+ return (
84
+ "💥 Image generation ran out of GPU memory. Try a smaller "
85
+ "Height/Width or fewer Inference Steps, then retry."
86
+ )
87
+ return (
88
+ "⚠️ Image generation failed. Please try again in a moment — if it "
89
+ "keeps happening, simplify your prompt or lower the resolution."
90
+ )
91
+
92
+
93
+ @spaces.GPU(duration=_estimate_duration)
94
+ def _generate_image_gpu(
95
  prompt: str,
96
  height: int,
97
  width: int,
 
99
  seed: int,
100
  randomize_seed: bool,
101
  ):
102
+ """The GPU-decorated worker. Runs only under a ZeroGPU allocation; the
103
+ allocator raises *before* this body if no GPU can be granted, which is why
104
+ the rewording lives in the plain `generate_image` wrapper below, not here.
 
 
 
 
 
 
105
  """
106
  if not prompt or not prompt.strip():
107
  raise gr.Error("Please enter a prompt.")
 
122
  return _save_image(image), int(seed)
123
 
124
 
125
+ def generate_image(
126
+ prompt: str,
127
+ height: int,
128
+ width: int,
129
+ num_inference_steps: int,
130
+ seed: int,
131
+ randomize_seed: bool,
132
+ ):
133
+ """Workflow-facing wrapper around the GPU worker.
134
+
135
+ Bound to the canvas as a `fn` operator node — the workflow calls this
136
+ Python function directly server-side, so the entire pipeline (frontend +
137
+ ZeroGPU) lives in a single Space. This non-GPU wrapper catches rejections
138
+ from the `@spaces.GPU` allocator (which fire before the worker body runs)
139
+ and rewords them into a clear, honest user-facing message.
140
+
141
+ Returns (image_dict, seed_used). The image is serialized to a /gradio_api
142
+ file URL so JSON serialization across the fn bridge succeeds; the
143
+ executor's `fromGradioOutput` turns the dict back into an image port value.
144
+ """
145
+ try:
146
+ return _generate_image_gpu(
147
+ prompt, height, width, num_inference_steps, seed, randomize_seed
148
+ )
149
+ except gr.Error:
150
+ # Already a user-facing validation message (e.g. empty prompt) — pass
151
+ # it through unchanged.
152
+ raise
153
+ except Exception as e:
154
+ # Allocator rejection (GPU limit / quota / OOM / etc.) — reword.
155
+ raise gr.Error(_friendly_gpu_error(e)) from e
156
+
157
+
158
+ # The workflow (workflow.json) wires `generate_image` as a `fn` operator:
159
  # Prompt, Height, Width, Inference Steps, Seed, Randomize Seed ─▶
160
  # generate_image (fn operator, kind="fn") ─▶ Output Image, Seed Used
161
  #