akhaliq HF Staff commited on
Commit
e436294
·
1 Parent(s): 52464dd

Add gr.Workflow app for Pruna-Qwen-Image-2.1 (ZeroGPU, gradio 6.28.0)

Browse files
Files changed (5) hide show
  1. README.md +2 -2
  2. app.py +113 -0
  3. requirements.txt +7 -0
  4. run.py +0 -6
  5. workflow.json +102 -1
README.md CHANGED
@@ -4,8 +4,8 @@ emoji: ⚡
4
  colorFrom: indigo
5
  colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 6.25.0
8
- app_file: run.py
9
  pinned: false
10
  hf_oauth: true
11
  hf_oauth_scopes:
 
4
  colorFrom: indigo
5
  colorTo: indigo
6
  sdk: gradio
7
+ sdk_version: 6.28.0
8
+ app_file: app.py
9
  pinned: false
10
  hf_oauth: true
11
  hf_oauth_scopes:
app.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """gr.Workflow app for PrunaAI/Pruna-Qwen-Image-2.1 (ZeroGPU).
2
+
3
+ Nodes on the canvas:
4
+ - "Generate Image": text-to-image with the 5- or 8-step LoRA adapter
5
+ - "Edit Image": image editing with an input image + prompt
6
+ """
7
+
8
+ import os
9
+
10
+ import torch
11
+ import spaces
12
+ import gradio as gr
13
+ from gradio.utils import get_upload_folder
14
+ from gradio_client import utils as client_utils
15
+ from PIL import Image
16
+ from diffusers import FlowMatchEulerDiscreteScheduler, QwenImage21Pipeline
17
+
18
+ SIGMAS = {
19
+ 5: [1.0, 0.94, 6 / 7, 2 / 3, 0.4],
20
+ 8: [1.0, 14 / 15, 6 / 7, 10 / 13, 2 / 3, 6 / 11, 0.4, 2 / 9],
21
+ }
22
+
23
+ # Load the base pipeline once at module level (ZeroGPU CUDA emulation makes
24
+ # this a no-op until a @spaces.GPU function actually runs).
25
+ pipe = QwenImage21Pipeline.from_pretrained(
26
+ "Qwen/Qwen-Image-2.1", torch_dtype=torch.bfloat16
27
+ ).to("cuda")
28
+ pipe.scheduler = FlowMatchEulerDiscreteScheduler.from_config(
29
+ pipe.scheduler.config,
30
+ use_dynamic_shifting=False,
31
+ shift=1.0,
32
+ shift_terminal=None,
33
+ )
34
+
35
+ _loaded_steps = None
36
+
37
+
38
+ def _ensure_adapter(steps: int):
39
+ """Load exactly one Pruna LoRA adapter matching the requested step count."""
40
+ global _loaded_steps
41
+ steps = 5 if int(steps) == 5 else 8
42
+ if _loaded_steps != steps:
43
+ pipe.unload_lora_weights()
44
+ pipe.load_lora_weights(
45
+ "PrunaAI/Pruna-Qwen-Image-2.1",
46
+ weight_name=f"p_qwen_image_2.1_{steps}step_v0.1.safetensors",
47
+ )
48
+ _loaded_steps = steps
49
+ return steps
50
+
51
+
52
+ def _coerce_image(image):
53
+ """Accept a PIL image, a filepath, or a Gradio/Workflow file-ref dict."""
54
+ if image is None or image == "":
55
+ return None
56
+ if isinstance(image, Image.Image):
57
+ return image.convert("RGB")
58
+ if isinstance(image, dict):
59
+ image = image.get("path") or image.get("url") or ""
60
+ return Image.open(str(image)).convert("RGB")
61
+
62
+
63
+ def _to_file(img: Image.Image) -> dict:
64
+ """Save a PIL image into Gradio's upload folder and return a file-ref dict
65
+ (same shape gr.Workflow uses internally for media node outputs)."""
66
+ directory = get_upload_folder()
67
+ os.makedirs(directory, exist_ok=True)
68
+ path = os.path.join(directory, f"pruna_{os.urandom(8).hex()}.png")
69
+ img.save(path)
70
+ url = f"/gradio_api/file={client_utils.encode_file_path(path)}"
71
+ return {"path": path, "url": url, "is_file": True}
72
+
73
+
74
+ @spaces.GPU(duration=120)
75
+ def generate(prompt: str, steps: int = 8, seed: int = 42) -> dict:
76
+ """Text-to-image with Pruna-Qwen-Image-2.1 (5 or 8 steps, no CFG)."""
77
+ steps = _ensure_adapter(steps)
78
+ out = pipe(
79
+ prompt=prompt,
80
+ width=1024,
81
+ height=1024,
82
+ generator=torch.Generator("cuda").manual_seed(int(seed)),
83
+ num_inference_steps=steps,
84
+ sigmas=SIGMAS[steps],
85
+ true_cfg_scale=1.0,
86
+ use_kv_cache=True,
87
+ ).images[0]
88
+ return _to_file(out)
89
+
90
+
91
+ @spaces.GPU(duration=120)
92
+ def edit(image, prompt: str, steps: int = 8, seed: int = 42) -> dict:
93
+ """Edit an image with Pruna-Qwen-Image-2.1 (5 or 8 steps, no CFG)."""
94
+ steps = _ensure_adapter(steps)
95
+ out = pipe(
96
+ prompt=prompt,
97
+ image=_coerce_image(image),
98
+ generator=torch.Generator("cuda").manual_seed(int(seed)),
99
+ num_inference_steps=steps,
100
+ sigmas=SIGMAS[steps],
101
+ true_cfg_scale=1.0,
102
+ use_kv_cache=True,
103
+ ).images[0]
104
+ return _to_file(out)
105
+
106
+
107
+ demo = gr.Workflow(
108
+ graph="workflow.json",
109
+ bind={"Generate Image": generate, "Edit Image": edit},
110
+ )
111
+
112
+ if __name__ == "__main__":
113
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ torch>=2.4.0
2
+ transformers>=5.17
3
+ accelerate
4
+ peft
5
+ pillow
6
+ spaces
7
+ git+https://github.com/huggingface/diffusers@6256aa7666cedd47443adc8f82da9a10e110b09c
run.py DELETED
@@ -1,6 +0,0 @@
1
- import gradio as gr
2
-
3
- demo = gr.Workflow()
4
-
5
- if __name__ == "__main__":
6
- demo.launch()
 
 
 
 
 
 
 
workflow.json CHANGED
@@ -1 +1,102 @@
1
- {"schema_version":"2","name":"My Workflow","runtime":{"default":"client"},"references":[],"operators":[],"subjects":[],"edges":[],"view":{"default":"canvas"}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": "2",
3
+ "name": "Pruna Qwen Image 2.1",
4
+ "references": [
5
+ {
6
+ "id": "ref_prompt",
7
+ "label": "Prompt",
8
+ "role": "reference",
9
+ "asset_type": "text",
10
+ "x": 40,
11
+ "y": 80,
12
+ "inputs": [{"id": "in", "label": "Text", "type": "text"}],
13
+ "outputs": [{"id": "out", "label": "Text", "type": "text"}],
14
+ "data": {"out": "A glowing neon shop sign that reads \"QWEN IMAGE 2.1\", mounted on a brick wall in a narrow city alley at night. Heavy rain, wet pavement reflecting pink and blue light, shallow depth of field, cinematic photograph."}
15
+ },
16
+ {
17
+ "id": "ref_steps",
18
+ "label": "Steps (5 or 8)",
19
+ "role": "reference",
20
+ "asset_type": "number",
21
+ "x": 40,
22
+ "y": 320,
23
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
24
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
25
+ "data": {"out": 8}
26
+ },
27
+ {
28
+ "id": "ref_seed",
29
+ "label": "Seed",
30
+ "role": "reference",
31
+ "asset_type": "number",
32
+ "x": 40,
33
+ "y": 480,
34
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
35
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
36
+ "data": {"out": 42}
37
+ }
38
+ ],
39
+ "operators": [
40
+ {
41
+ "id": "op_generate",
42
+ "label": "Generate Image",
43
+ "role": "operator",
44
+ "kind": "fn",
45
+ "fn": "Generate Image",
46
+ "x": 460,
47
+ "y": 100,
48
+ "inputs": [
49
+ {"id": "prompt", "label": "Prompt", "type": "text", "required": true},
50
+ {"id": "steps", "label": "Steps", "type": "number"},
51
+ {"id": "seed", "label": "Seed", "type": "number"}
52
+ ],
53
+ "outputs": [{"id": "out_0", "label": "Image", "type": "image", "output_index": 0}]
54
+ },
55
+ {
56
+ "id": "op_edit",
57
+ "label": "Edit Image",
58
+ "role": "operator",
59
+ "kind": "fn",
60
+ "fn": "Edit Image",
61
+ "x": 860,
62
+ "y": 120,
63
+ "inputs": [
64
+ {"id": "image", "label": "Image", "type": "image", "required": true},
65
+ {"id": "prompt", "label": "Edit Prompt", "type": "text", "required": true},
66
+ {"id": "steps", "label": "Steps", "type": "number"},
67
+ {"id": "seed", "label": "Seed", "type": "number"}
68
+ ],
69
+ "outputs": [{"id": "out_0", "label": "Edited Image", "type": "image", "output_index": 0}]
70
+ }
71
+ ],
72
+ "subjects": [
73
+ {
74
+ "id": "sub_generated",
75
+ "label": "Generated Image",
76
+ "role": "subject",
77
+ "asset_type": "image",
78
+ "x": 1260,
79
+ "y": 80,
80
+ "inputs": [{"id": "in", "label": "Image", "type": "image"}],
81
+ "outputs": [{"id": "out", "label": "Image", "type": "image"}]
82
+ },
83
+ {
84
+ "id": "sub_edited",
85
+ "label": "Edited Image",
86
+ "role": "subject",
87
+ "asset_type": "image",
88
+ "x": 1260,
89
+ "y": 380,
90
+ "inputs": [{"id": "in", "label": "Image", "type": "image"}],
91
+ "outputs": [{"id": "out", "label": "Image", "type": "image"}]
92
+ }
93
+ ],
94
+ "edges": [
95
+ {"id": "e1", "from_node_id": "ref_prompt", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "prompt", "type": "text"},
96
+ {"id": "e2", "from_node_id": "ref_steps", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "steps", "type": "number"},
97
+ {"id": "e3", "from_node_id": "ref_seed", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "seed", "type": "number"},
98
+ {"id": "e4", "from_node_id": "op_generate", "from_port_id": "out_0", "to_node_id": "op_edit", "to_port_id": "image", "type": "image"},
99
+ {"id": "e5", "from_node_id": "op_generate", "from_port_id": "out_0", "to_node_id": "sub_generated", "to_port_id": "in", "type": "image"},
100
+ {"id": "e6", "from_node_id": "op_edit", "from_port_id": "out_0", "to_node_id": "sub_edited", "to_port_id": "in", "type": "image"}
101
+ ]
102
+ }