simapeng's picture
6th
9b941df
import io
import tempfile
import zipfile
import random
import torch
import spaces
import gradio as gr
from diffusers import DiffusionPipeline
MAX_SEED = 2**32 - 1
# ===== Custom aesthetic =====
# Neo-noir dusk palette with cyan + amber accents, glass panels, and subtle grain.
CUSTOM_CSS = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
:root {
/* Light Mode (Professional & Clean) */
--bg: #fdfdfd;
--panel: rgba(255, 255, 255, 0.95);
--card: #ffffff;
--border: #e5e7eb;
--border-hover: #d1d5db;
--text: #111827;
--text-secondary: #4b5563;
--muted: #9ca3af;
--accent: #0f172a; /* Dark sleek accent for professionalism */
--accent-hover: #1e293b;
--accent-text: #ffffff;
--primary-gradient: linear-gradient(135deg, #0f172a 0%, #334155 100%);
--glow: 0 0 0 transparent;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.03);
--radius: 12px;
--input-bg: #ffffff;
--input-border: #e2e8f0;
--checkbox-bg: #f1f5f9;
--body-bg: #f8fafc; /* Very subtle cool gray */
--font-heading: 'Inter', -apple-system, sans-serif;
--font-body: 'Inter', -apple-system, sans-serif;
}
.dark {
/* Dark Mode (Neo-Noir Polished) */
--bg: #05080f;
--panel: rgba(12, 18, 32, 0.85);
--card: rgba(18, 28, 46, 0.70);
--border: rgba(36, 224, 194, 0.15);
--border-hover: rgba(36, 224, 194, 0.3);
--text: #e9f3ff;
--text-secondary: #94a3b8;
--muted: #64748b;
--accent: #24e0c2;
--accent-hover: #18cdb0;
--accent-text: #041019;
--primary-gradient: linear-gradient(120deg, #24e0c2 0%, #ffb347 100%);
--glow: 0 8px 32px rgba(36, 224, 194, 0.12);
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 20px 40px -5px rgba(0, 0, 0, 0.4);
--radius: 16px;
--input-bg: rgba(255,255,255,0.03);
--input-border: rgba(255,255,255,0.08);
--checkbox-bg: #0d1829;
--body-bg: radial-gradient(circle at 20% 20%, rgba(36, 224, 194, 0.06), transparent 35%),
radial-gradient(circle at 82% 12%, rgba(0, 156, 196, 0.06), transparent 35%),
linear-gradient(145deg, #05080f 0%, #080f1e 100%);
--font-heading: 'Inter', -apple-system, sans-serif;
--font-body: 'Inter', -apple-system, sans-serif;
}
body, .gradio-container {
font-family: var(--font-body) !important;
background: var(--body-bg) !important;
color: var(--text);
min-height: 100vh;
}
/* Titles & Typography */
.gradio-container .prose h1,
.gradio-container .prose h2,
.gradio-container .prose h3 {
font-family: var(--font-heading);
letter-spacing: -0.025em;
font-weight: 700;
color: var(--text);
}
.gradio-container .prose h1 {
font-size: 2.25rem;
margin-bottom: 0.5rem;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
display: inline-block;
}
.gradio-container * { letter-spacing: -0.01em; }
/* Panels & Cards */
.gr-block, .gr-panel, .gr-group {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-sm);
backdrop-filter: blur(8px);
transition: box-shadow 0.2s ease, border-color 0.2s ease;
}
.hero-card {
background: var(--card);
border: 1px solid var(--border);
padding: 24px;
border-radius: var(--radius);
box-shadow: var(--shadow-md);
position: relative;
overflow: hidden;
}
.tagline {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 14px;
background: var(--input-bg);
border: 1px solid var(--border);
border-radius: 999px;
font-size: 0.875rem;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 12px;
}
.hero-card p {
color: var(--text-secondary);
font-size: 1.05rem;
line-height: 1.6;
max-width: 65ch;
}
/* Inputs */
textarea, input:not([type='checkbox']):not([type='radio']),
.gr-input, .gr-textbox, .gr-number, .gr-slider input {
background: var(--input-bg) !important;
border: 1px solid var(--input-border) !important;
border-radius: 10px !important;
color: var(--text) !important;
font-family: var(--font-body);
transition: all 0.2s ease;
}
textarea:focus, input:focus, .gr-input:focus-within {
border-color: var(--text-secondary) !important;
box-shadow: 0 0 0 2px rgba(var(--accent), 0.1);
}
label, .gr-box label {
color: var(--text-secondary) !important;
font-weight: 600;
font-size: 0.875rem;
margin-bottom: 6px;
text-transform: none !important;
}
/* Sliders */
.gr-slider input[type='range'] {
accent-color: var(--accent);
}
/* Buttons */
.gr-button-primary, button.primary {
background: var(--primary-gradient) !important;
color: var(--accent-text) !important;
font-weight: 600 !important;
border: 1px solid rgba(255,255,255,0.1) !important;
box-shadow: var(--shadow-md);
border-radius: 10px !important;
padding: 10px 24px;
transition: transform 0.1s, box-shadow 0.2s;
}
.gr-button-primary:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-lg);
filter: brightness(1.1);
}
.gr-button-secondary, button.secondary, .gr-downloadbutton {
background: var(--input-bg) !important;
border: 1px solid var(--border) !important;
color: var(--text) !important;
font-weight: 500;
border-radius: 10px !important;
box-shadow: var(--shadow-sm);
}
.gr-button-secondary:hover {
border-color: var(--border-hover) !important;
background: var(--card) !important;
}
.gr-downloadbutton, .gr-downloadbutton > button { width: 100%; }
/* Gallery */
.gr-gallery {
background: var(--input-bg);
border-radius: var(--radius);
border: 1px solid var(--border);
padding: 8px;
}
.gr-gallery .thumbnail-item {
border-radius: 8px;
overflow: hidden;
box-shadow: var(--shadow-sm);
border: 1px solid transparent;
transition: all 0.2s;
}
.gr-gallery .thumbnail-item:hover {
box-shadow: var(--shadow-md);
transform: scale(1.02);
}
.gr-gallery img { object-fit: cover; }
/* Footer */
.footer-note {
color: var(--muted);
font-size: 0.875rem;
text-align: center;
margin-top: 2rem;
opacity: 0.8;
}
.footer-note a {
color: var(--text-secondary);
text-decoration: none;
border-bottom: 1px dotted var(--muted);
}
.footer-note a:hover {
color: var(--accent);
border-bottom-style: solid;
}
"""
# Load the pipeline once at startup
print("Loading Z-Image-Turbo pipeline...")
pipe = DiffusionPipeline.from_pretrained(
"Tongyi-MAI/Z-Image-Turbo",
torch_dtype=torch.bfloat16,
low_cpu_mem_usage=False,
)
pipe.to("cuda")
'# ======== AoTI compilation + FA3 ======== (disabled on HF to avoid outdated AOTI/FA3 package errors)'
# pipe.transformer.layers._repeated_blocks = ["ZImageTransformerBlock"]
# spaces.aoti_blocks_load(pipe.transformer.layers, "zerogpu-aoti/Z-Image", variant="fa3")
print("Pipeline loaded!")
@spaces.GPU
def generate_image(
prompt,
negative_prompt,
height,
width,
images_count,
num_inference_steps,
guidance_scale,
seed,
randomize_seed,
progress=gr.Progress(track_tqdm=True),
):
"""Generate N images using a deterministic seed cascade (x1..xN)."""
if randomize_seed:
seed = random.randint(0, MAX_SEED)
base_seed = int(seed) % MAX_SEED
if base_seed < 0:
base_seed += MAX_SEED
# Cap to prevent excessive VRAM usage / latency spikes on the demo space
images_count = max(1, min(int(images_count), 12))
seeds = [(base_seed * i) % MAX_SEED for i in range(1, images_count + 1)]
neg_prompt = None
if isinstance(negative_prompt, str) and negative_prompt.strip():
neg_prompt = negative_prompt
images = []
image_paths = []
for s in seeds:
generator = torch.Generator("cuda").manual_seed(int(s))
image = pipe(
prompt=prompt,
negative_prompt=neg_prompt,
height=int(height),
width=int(width),
num_inference_steps=int(num_inference_steps),
guidance_scale=float(guidance_scale), # 0.0 is recommended default for Turbo
generator=generator,
).images[0]
images.append(image)
tmp_img = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
image.save(tmp_img.name, format="PNG")
image_paths.append(tmp_img.name)
return images, ", ".join(str(s) for s in seeds), image_paths, base_seed
def append_history(new_images, history):
"""Append new images to the history state."""
if history is None:
history = []
updated_history = history + new_images
return updated_history, updated_history
def package_zip(image_paths):
"""Pack the current image list into a ZIP file for download."""
if not image_paths:
raise gr.Error("No images in history to download.")
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".zip")
with zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as zf:
for idx, path in enumerate(image_paths, start=1):
# Store as image_001.png, image_002.png, ...
zf.write(path, arcname=f"image_{idx:03d}.png")
tmp.flush()
return tmp.name
# Example prompts
examples = [
["Astronaut riding a horse on Mars, cinematic lighting, sci-fi concept art, highly detailed"],
["Portrait of a wise old wizard with a long white beard, holding a glowing crystal staff, magical forest background"],
]
# Build the Gradio interface
# Build the Gradio interface
with gr.Blocks(title="Z-Image-Turbo Demo", css=CUSTOM_CSS, analytics_enabled=False) as demo:
image_state = gr.State([])
history_state = gr.State([])
gr.Markdown(
"""
<div class="hero-card">
<div class="tagline">⚡ Turbo diffusion · 8 steps · CUDA ready</div>
<h1>Z‑Image Turbo Studio</h1>
<p>Draft up to twelve stylized candidates in one pass. Neo‑noir gradients, glass panels, and crisp typography keep the tooling out of your way while you explore ideas.</p>
</div>
""",
sanitize_html=False,
)
with gr.Row():
with gr.Column(scale=1):
prompt = gr.Textbox(
label="Prompt",
placeholder="e.g. bioluminescent reef city at dusk, cinematic, anamorphic glow",
lines=4,
)
negative_prompt = gr.Textbox(
label="Negative Prompt",
placeholder="noise, blur, extra limbs, text watermark",
lines=3,
)
with gr.Row():
height = gr.Slider(
minimum=512,
maximum=2048,
value=1024,
step=64,
label="Height",
)
width = gr.Slider(
minimum=512,
maximum=2048,
value=1024,
step=64,
label="Width",
)
with gr.Row():
num_inference_steps = gr.Slider(
minimum=1,
maximum=20,
value=9,
step=1,
label="Inference Steps",
info="9 steps → 8 DiT forwards",
)
images_count = gr.Slider(
minimum=1,
maximum=12,
value=4,
step=1,
label="Images",
info="1–12 (higher counts use more VRAM)",
)
guidance_scale = gr.Slider(
minimum=0.0,
maximum=7.0,
value=0.0,
step=0.1,
label="CFG Guidance Scale",
info="0 = no CFG (recommended for Turbo models)",
)
with gr.Row():
seed = gr.Number(
label="Base Seed",
value=42,
precision=0,
)
randomize_seed = gr.Checkbox(
label="Randomize",
value=True,
interactive=True,
)
generate_btn = gr.Button("🚀 Generate", variant="primary", size="lg")
with gr.Column(scale=1):
output_images = gr.Gallery(
label="Generated Grid",
columns=4,
rows=None,
preview=True,
)
used_seeds = gr.Textbox(
label="Seed Cascade (x1 · x2 · ... · xN)",
interactive=False,
)
history_gallery = gr.Gallery(
label="History",
columns=6,
rows=None,
preview=True,
object_fit="cover"
)
download_btn = gr.DownloadButton(
label="📦 Download All History (ZIP)",
)
gr.Markdown("### 💡 Quick Prompts")
gr.Examples(
examples=examples,
inputs=[prompt],
cache_examples=False,
)
gr.Markdown(
"""
<div class="footer-note">
Model: Tongyi-MAI/Z-Image-Turbo (Apache 2.0). Demo by <a href="https://z-image-turbo.tech" target="_blank">https://z-image-turbo.tech</a>
</div>
""",
sanitize_html=False,
)
# Connect the generate button
generate_btn.click(
fn=generate_image,
inputs=[prompt, negative_prompt, height, width, images_count, num_inference_steps, guidance_scale, seed, randomize_seed],
outputs=[output_images, used_seeds, image_state, seed],
).success(
fn=append_history,
inputs=[image_state, history_state],
outputs=[history_state, history_gallery],
)
# Also allow generating by pressing Enter in the prompt box
prompt.submit(
fn=generate_image,
inputs=[prompt, negative_prompt, height, width, images_count, num_inference_steps, guidance_scale, seed, randomize_seed],
outputs=[output_images, used_seeds, image_state, seed],
).success(
fn=append_history,
inputs=[image_state, history_state],
outputs=[history_state, history_gallery],
)
download_btn.click(
fn=package_zip,
inputs=[history_state],
outputs=[download_btn],
)
if __name__ == "__main__":
demo.launch(mcp_server=True, show_error=True)