GLM 5.2 commited on
Commit
639e5b2
·
1 Parent(s): ad4a177

Convert app to gr.Workflow calling Z-Image-Turbo via HF Inference API

Browse files

Replace the local-diffusers/GPU Blocks app with a gr.Workflow pipeline:
Prompt (reference) -> Z-Image-Turbo (model operator, fal-ai provider)
-> Output Image (subject). Calls InferenceClient.text_to_image on
Tongyi-MAI/Z-Image-Turbo, so no GPU or local weights are needed.

- app.py: gr.Workflow(graph=workflow.json, bind={"text_to_image": ...})
- workflow.json: pre-wired schema-v2 canvas (model operator + edges)
- requirements.txt: drop diffusers/torch/kernels, add huggingface_hub
- README.md: pin gradio 6.1.0 (ships gr.Workflow), set hf_oauth: true

Files changed (4) hide show
  1. README.md +57 -2
  2. app.py +28 -250
  3. requirements.txt +3 -5
  4. workflow.json +88 -0
README.md CHANGED
@@ -4,9 +4,64 @@ emoji: 🖼️
4
  colorFrom: yellow
5
  colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 6.0.1
8
  app_file: app.py
9
  pinned: true
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  colorFrom: yellow
5
  colorTo: yellow
6
  sdk: gradio
7
+ sdk_version: 6.1.0
8
  app_file: app.py
9
  pinned: true
10
+ hf_oauth: true
11
  ---
12
 
13
+ # Z-Image-Turbo (Gradio Workflow)
14
+
15
+ A visual, node-based image-generation app built with `gr.Workflow`. It calls
16
+ the [Tongyi-MAI/Z-Image-Turbo](https://huggingface.co/Tongyi-MAI/Z-Image-Turbo)
17
+ model through the Hugging Face Inference API (served by the `fal-ai` provider)
18
+ — no GPU or local weights required.
19
+
20
+ ## How it works
21
+
22
+ The workflow (defined in [`workflow.json`](./workflow.json)) is three nodes:
23
+
24
+ 1. **Prompt** (reference) — the text prompt you want to render.
25
+ 2. **Z-Image-Turbo** (operator, `kind: "model"`) — calls the model via
26
+ `InferenceClient.text_to_image`.
27
+ 3. **Output Image** (subject) — the generated image, also exposed as the
28
+ `/output_image` API endpoint.
29
+
30
+ Edit the topology on the canvas (drag nodes, change the prompt, rewire) and
31
+ hit **Run**. Changes are saved back to `workflow.json`.
32
+
33
+ ## Running locally
34
+
35
+ ```bash
36
+ pip install -r requirements.txt
37
+ huggingface_hub login # provides the HF token used by the InferenceClient
38
+ python app.py
39
+ ```
40
+
41
+ Open the **write-access link** printed at launch to edit the workflow; plain
42
+ local/share URLs open it read-only.
43
+
44
+ ## Deploying
45
+
46
+ This is a standard Gradio app — deploy it with:
47
+
48
+ ```bash
49
+ gradio deploy
50
+ ```
51
+
52
+ `hf_oauth: true` is set so that, on a Space, each visitor signs in with their
53
+ own HF account and the model call runs under their own token/quota. The Space
54
+ owner can edit and save the workflow; visitors get a read-only view and can
55
+ run the pipeline.
56
+
57
+ ## API access
58
+
59
+ Every Workflow app is a Gradio app, so it exposes a REST endpoint per output
60
+ (subject) node — e.g. `/output_image`:
61
+
62
+ ```python
63
+ from gradio_client import Client
64
+
65
+ client = Client("your-username/your-space")
66
+ client.view_api() # list endpoints and parameters
67
+ ```
app.py CHANGED
@@ -1,260 +1,38 @@
1
- import torch
2
- import spaces
3
- import gradio as gr
4
- from diffusers import DiffusionPipeline
5
 
6
- # Load the pipeline once at startup
7
- print("Loading Z-Image-Turbo pipeline...")
8
- pipe = DiffusionPipeline.from_pretrained(
9
- "Tongyi-MAI/Z-Image-Turbo",
10
- torch_dtype=torch.bfloat16,
11
- low_cpu_mem_usage=False,
 
 
 
 
 
 
 
12
  )
13
- pipe.to("cuda")
14
 
15
- # ======== AoTI compilation + FA3 ========
16
- # pipe.transformer.layers._repeated_blocks = ["ZImageTransformerBlock"]
17
- # spaces.aoti_blocks_load(pipe.transformer.layers, "zerogpu-aoti/Z-Image", variant="fa3")
18
 
19
- print("Pipeline loaded!")
 
20
 
21
- @spaces.GPU
22
- def generate_image(prompt, height, width, num_inference_steps, seed, randomize_seed, progress=gr.Progress(track_tqdm=True)):
23
- """Generate an image from the given prompt."""
24
- if randomize_seed:
25
- seed = torch.randint(0, 2**32 - 1, (1,)).item()
26
-
27
- generator = torch.Generator("cuda").manual_seed(int(seed))
28
- image = pipe(
29
- prompt=prompt,
30
- height=int(height),
31
- width=int(width),
32
- num_inference_steps=int(num_inference_steps),
33
- guidance_scale=0.0,
34
- generator=generator,
35
- ).images[0]
36
-
37
- return image, seed
38
 
39
- # Example prompts
40
- examples = [
41
- ["Young Chinese woman in red Hanfu, intricate embroidery. Impeccable makeup, red floral forehead pattern. Elaborate high bun, golden phoenix headdress, red flowers, beads. Holds round folding fan with lady, trees, bird. Neon lightning-bolt lamp, bright yellow glow, above extended left palm. Soft-lit outdoor night background, silhouetted tiered pagoda, blurred colorful distant lights."],
42
- ["A majestic dragon soaring through clouds at sunset, scales shimmering with iridescent colors, detailed fantasy art style"],
43
- ["Cozy coffee shop interior, warm lighting, rain on windows, plants on shelves, vintage aesthetic, photorealistic"],
44
- ["Astronaut riding a horse on Mars, cinematic lighting, sci-fi concept art, highly detailed"],
45
- ["Portrait of a wise old wizard with a long white beard, holding a glowing crystal staff, magical forest background"],
46
- ]
47
 
48
- # Custom theme with modern aesthetics (Gradio 6)
49
- custom_theme = gr.themes.Soft(
50
- primary_hue="yellow",
51
- secondary_hue="amber",
52
- neutral_hue="slate",
53
- font=gr.themes.GoogleFont("Inter"),
54
- text_size="lg",
55
- spacing_size="md",
56
- radius_size="lg"
57
- ).set(
58
- button_primary_background_fill="*primary_500",
59
- button_primary_background_fill_hover="*primary_600",
60
- block_title_text_weight="600",
61
  )
62
 
63
- # Build the Gradio interface
64
- with gr.Blocks(fill_height=True) as demo:
65
- # Header
66
- gr.Markdown(
67
- """
68
- # 🎨 Z-Image-Turbo
69
- **Ultra-fast AI image generation** • Generate stunning images in just 8 steps
70
- """,
71
- elem_classes="header-text"
72
- )
73
-
74
- with gr.Row(equal_height=False):
75
- # Left column - Input controls
76
- with gr.Column(scale=1, min_width=320):
77
- prompt = gr.Textbox(
78
- label="✨ Your Prompt",
79
- placeholder="Describe the image you want to create...",
80
- lines=5,
81
- max_lines=10,
82
- autofocus=True,
83
- )
84
-
85
- with gr.Accordion("⚙️ Advanced Settings", open=False):
86
- with gr.Row():
87
- height = gr.Slider(
88
- minimum=512,
89
- maximum=2048,
90
- value=1024,
91
- step=64,
92
- label="Height",
93
- info="Image height in pixels"
94
- )
95
- width = gr.Slider(
96
- minimum=512,
97
- maximum=2048,
98
- value=1024,
99
- step=64,
100
- label="Width",
101
- info="Image width in pixels"
102
- )
103
-
104
- num_inference_steps = gr.Slider(
105
- minimum=1,
106
- maximum=20,
107
- value=9,
108
- step=1,
109
- label="Inference Steps",
110
- info="9 steps = 8 DiT forwards (recommended)"
111
- )
112
-
113
- with gr.Row():
114
- randomize_seed = gr.Checkbox(
115
- label="🎲 Random Seed",
116
- value=True,
117
- )
118
- seed = gr.Number(
119
- label="Seed",
120
- value=42,
121
- precision=0,
122
- visible=False,
123
- )
124
-
125
- def toggle_seed(randomize):
126
- return gr.Number(visible=not randomize)
127
-
128
- randomize_seed.change(
129
- toggle_seed,
130
- inputs=[randomize_seed],
131
- outputs=[seed]
132
- )
133
-
134
- generate_btn = gr.Button(
135
- "🚀 Generate Image",
136
- variant="primary",
137
- size="lg",
138
- scale=1
139
- )
140
-
141
- # Example prompts
142
- gr.Examples(
143
- examples=examples,
144
- inputs=[prompt],
145
- label="💡 Try these prompts",
146
- examples_per_page=5,
147
- )
148
-
149
- # Right column - Output
150
- with gr.Column(scale=1, min_width=320):
151
- output_image = gr.Image(
152
- label="Generated Image",
153
- type="pil",
154
- format="png",
155
- show_label=False,
156
- height=600,
157
- buttons=["download", "share"],
158
- )
159
-
160
- used_seed = gr.Number(
161
- label="🎲 Seed Used",
162
- interactive=False,
163
- container=True,
164
- )
165
-
166
- # Footer credits
167
- gr.Markdown(
168
- """
169
- ---
170
- <div style="text-align: center; opacity: 0.7; font-size: 0.9em; margin-top: 1rem;">
171
- <strong>Model:</strong> <a href="https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" target="_blank">Tongyi-MAI/Z-Image-Turbo</a> (Apache 2.0 License) •
172
- <strong>Demo by:</strong> <a href="https://x.com/realmrfakename" target="_blank">@mrfakename</a> •
173
- <strong>Redesign by:</strong> AnyCoder •
174
- <strong>Optimizations:</strong> <a href="https://huggingface.co/multimodalart" target="_blank">@multimodalart</a> (FA3 + AoTI)
175
- </div>
176
- """,
177
- elem_classes="footer-text"
178
- )
179
-
180
- # Connect the generate button
181
- generate_btn.click(
182
- fn=generate_image,
183
- inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed],
184
- outputs=[output_image, used_seed],
185
- )
186
-
187
- # Also allow generating by pressing Enter in the prompt box
188
- prompt.submit(
189
- fn=generate_image,
190
- inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed],
191
- outputs=[output_image, used_seed],
192
- )
193
-
194
  if __name__ == "__main__":
195
- demo.launch(
196
- theme=custom_theme,
197
- css="""
198
- .header-text h1 {
199
- font-size: 2.5rem !important;
200
- font-weight: 700 !important;
201
- margin-bottom: 0.5rem !important;
202
- background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
203
- -webkit-background-clip: text;
204
- -webkit-text-fill-color: transparent;
205
- background-clip: text;
206
- }
207
-
208
- .header-text p {
209
- font-size: 1.1rem !important;
210
- color: #64748b !important;
211
- margin-top: 0 !important;
212
- }
213
-
214
- .footer-text {
215
- padding: 1rem 0;
216
- }
217
-
218
- .footer-text a {
219
- color: #f59e0b !important;
220
- text-decoration: none !important;
221
- font-weight: 500;
222
- }
223
-
224
- .footer-text a:hover {
225
- text-decoration: underline !important;
226
- }
227
-
228
- /* Mobile optimizations */
229
- @media (max-width: 768px) {
230
- .header-text h1 {
231
- font-size: 1.8rem !important;
232
- }
233
-
234
- .header-text p {
235
- font-size: 1rem !important;
236
- }
237
- }
238
-
239
- /* Smooth transitions */
240
- button, .gr-button {
241
- transition: all 0.2s ease !important;
242
- }
243
-
244
- button:hover, .gr-button:hover {
245
- transform: translateY(-1px);
246
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
247
- }
248
-
249
- /* Better spacing */
250
- .gradio-container {
251
- max-width: 1400px !important;
252
- margin: 0 auto !important;
253
- }
254
- """,
255
- footer_links=[
256
- "api",
257
- "gradio"
258
- ],
259
- mcp_server=True
260
- )
 
1
+ import os
 
 
 
2
 
3
+ import gradio as gr
4
+ from huggingface_hub import InferenceClient
5
+
6
+ # Z-Image-Turbo is served through the Hugging Face Inference API via the
7
+ # fal-ai provider. The token is provided by the Space (HF_TOKEN secret) or,
8
+ # locally, by `huggingface_hub login`. On a Space with `hf_oauth: true`,
9
+ # the workflow canvas forwards each visitor's own OAuth token to the model
10
+ # call, so they run inference under their own account/quota.
11
+ _HF_TOKEN = os.environ.get("HF_TOKEN")
12
+ _client = InferenceClient(
13
+ provider="fal-ai",
14
+ api_key=_HF_TOKEN,
15
+ model="Tongyi-MAI/Z-Image-Turbo",
16
  )
 
17
 
 
 
 
18
 
19
+ def text_to_image(prompt: str):
20
+ """Generate an image from a text prompt using Z-Image-Turbo.
21
 
22
+ Exposed as a bound node on the workflow canvas and as the workflow's
23
+ `/text_to_image` API endpoint (the subject node name derives from this).
24
+ Guidance is fixed at 0 (the Turbo models use no CFG) and the model
25
+ defaults to ~8 effective denoising steps.
26
+ """
27
+ if not prompt or not prompt.strip():
28
+ raise gr.Error("Please enter a prompt.")
29
+ return _client.text_to_image(prompt, model="Tongyi-MAI/Z-Image-Turbo")
 
 
 
 
 
 
 
 
 
30
 
 
 
 
 
 
 
 
 
31
 
32
+ demo = gr.Workflow(
33
+ graph="workflow.json",
34
+ bind={"text_to_image": text_to_image},
 
 
 
 
 
 
 
 
 
 
35
  )
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  if __name__ == "__main__":
38
+ demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,5 +1,3 @@
1
- gradio
2
- git+https://github.com/huggingface/diffusers
3
- transformers
4
- kernels
5
- gradio[mcp]
 
1
+ gradio>=6.1
2
+ huggingface_hub>=0.36.0
3
+ gradio[mcp]
 
 
workflow.json ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": "2",
3
+ "name": "Z-Image-Turbo",
4
+ "description": "Ultra-fast AI image generation with Z-Image-Turbo via the Hugging Face Inference API (fal-ai provider).",
5
+ "runtime": { "default": "client" },
6
+ "view": { "default": "canvas" },
7
+ "references": [
8
+ {
9
+ "id": "ref_prompt",
10
+ "label": "Prompt",
11
+ "role": "reference",
12
+ "asset_type": "text",
13
+ "inputs": [
14
+ { "id": "in", "label": "Prompt", "type": "text" }
15
+ ],
16
+ "outputs": [
17
+ { "id": "out", "label": "Prompt", "type": "text" }
18
+ ],
19
+ "x": 80,
20
+ "y": 160,
21
+ "width": 240,
22
+ "height": 120,
23
+ "data": {
24
+ "out": "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k"
25
+ }
26
+ }
27
+ ],
28
+ "operators": [
29
+ {
30
+ "id": "op_zimage",
31
+ "label": "Z-Image-Turbo",
32
+ "role": "operator",
33
+ "kind": "model",
34
+ "source": "hf://Tongyi-MAI/Z-Image-Turbo",
35
+ "model_id": "Tongyi-MAI/Z-Image-Turbo",
36
+ "pipeline_tag": "text-to-image",
37
+ "provider": "fal-ai",
38
+ "inputs": [
39
+ { "id": "in_0", "label": "Prompt", "type": "text", "required": true }
40
+ ],
41
+ "outputs": [
42
+ { "id": "out_0", "label": "Image", "type": "image", "output_index": 0 }
43
+ ],
44
+ "x": 440,
45
+ "y": 150,
46
+ "width": 260,
47
+ "height": 130,
48
+ "data": {}
49
+ }
50
+ ],
51
+ "subjects": [
52
+ {
53
+ "id": "sub_image",
54
+ "label": "Output Image",
55
+ "role": "subject",
56
+ "asset_type": "image",
57
+ "inputs": [
58
+ { "id": "in", "label": "Image", "type": "image" }
59
+ ],
60
+ "outputs": [
61
+ { "id": "out", "label": "Image", "type": "image" }
62
+ ],
63
+ "x": 800,
64
+ "y": 160,
65
+ "width": 240,
66
+ "height": 120,
67
+ "data": {}
68
+ }
69
+ ],
70
+ "edges": [
71
+ {
72
+ "id": "e_prompt_to_model",
73
+ "from_node_id": "ref_prompt",
74
+ "from_port_id": "out",
75
+ "to_node_id": "op_zimage",
76
+ "to_port_id": "in_0",
77
+ "type": "text"
78
+ },
79
+ {
80
+ "id": "e_model_to_output",
81
+ "from_node_id": "op_zimage",
82
+ "from_port_id": "out_0",
83
+ "to_node_id": "sub_image",
84
+ "to_port_id": "in",
85
+ "type": "image"
86
+ }
87
+ ]
88
+ }