Spaces:
Running on Zero
Running on Zero
Add gr.Workflow app for Pruna-Qwen-Image-2.1 (ZeroGPU, gradio 6.28.0)
Browse files- README.md +2 -2
- app.py +113 -0
- requirements.txt +7 -0
- run.py +0 -6
- 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.
|
| 8 |
-
app_file:
|
| 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 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
}
|