akhaliq's picture
akhaliq HF Staff
Handle None for unconnected number ports
d41bbfc
Raw History Blame
2.94 kB
"""Qwen-Image 2.1 — Gradio Workflow on ZeroGPU.
A node-based canvas (gr.Workflow) exposing the diffusers QwenImage21Pipeline:
- text_to_image: prompt -> image
- edit_image: condition image + instruction -> edited image (chained after
text_to_image, or fed from an uploaded image reference)
The pipeline is placed on `cuda` at module level: outside @spaces.GPU functions
PyTorch runs in CUDA emulation mode, and a real ZeroGPU is attached only while
a decorated function executes.
"""
import os
import tempfile
import gradio as gr
import spaces
import torch
from PIL import Image
from diffusers import QwenImage21Pipeline
MODEL_ID = os.environ.get("QWEN_IMAGE_MODEL", "Qwen/Qwen-Image-2.1")
# The model repo is private/gated. Per the Hub auth docs, the HF_TOKEN
# environment variable is used implicitly for all Hub requests and takes
# priority over any stored token — so set HF_TOKEN as a Space secret
# (Settings -> Secrets) with a token whose account has access to the repo.
if not os.environ.get("HF_TOKEN"):
raise RuntimeError(
"HF_TOKEN is not set. Add it as a Space secret (Settings -> Secrets) "
f"with read access to {MODEL_ID}."
)
pipe = QwenImage21Pipeline.from_pretrained(MODEL_ID, dtype=torch.bfloat16)
pipe.to("cuda")
def _to_pil(image) -> Image.Image:
"""Accept whatever the canvas hands a bound function for an image port:
a PIL image, a file path, or Gradio's {path, url, is_file} file dict."""
if isinstance(image, Image.Image):
return image
if isinstance(image, dict):
image = image.get("path") or image.get("url")
if isinstance(image, str):
return Image.open(image)
raise ValueError(f"Unsupported image input: {type(image)!r}")
def _save(image: Image.Image) -> str:
path = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
image.save(path)
return path
def _steps(value, default: int = 40) -> int:
# Unconnected number ports arrive as None; function defaults are not
# applied because the workflow executor passes arguments positionally.
return default if value is None else int(value)
@spaces.GPU(duration=120)
def text_to_image(prompt: str, steps: int = 40) -> str:
"""Generate an image from a text prompt with Qwen-Image 2.1."""
image = pipe(prompt, num_inference_steps=_steps(steps)).images[0]
return _save(image)
@spaces.GPU(duration=120)
def edit_image(image, instruction: str, steps: int = 40) -> str:
"""Edit a condition image following an instruction (image-conditioned
generation with Qwen-Image 2.1)."""
edited = pipe(instruction, image=_to_pil(image), num_inference_steps=_steps(steps)).images[0]
return _save(edited)
demo = gr.Workflow(
graph=os.path.join(os.path.dirname(__file__), "workflow.json"),
bind={"text_to_image": text_to_image, "edit_image": edit_image},
)
if __name__ == "__main__":
demo.launch()