Z-Anime-CPU / app.py
Nekochu's picture
Z-Anime 6B CPU: distill 8-step Q5_0, Qwen3-4B Q8_0, euler_a, beta schedule
736cf48
raw
history blame
5.01 kB
"""Z-Anime 6B Image Generation (CPU) via sd-cli binary"""
import os, time, subprocess, tempfile, threading
from PIL import Image
import gradio as gr
# ---------------------------------------------------------------------------
# Model paths (downloaded at build time)
# ---------------------------------------------------------------------------
DIFFUSION = "/app/models/z-anime-8steps-q5_0.gguf"
LLM = "/app/models/qwen3_4b_q8_0.gguf"
VAE = "/app/models/ae.safetensors"
RESOLUTIONS = ["512x512", "768x512", "512x768"]
STEPS = 8
CFG = 1.0
TIMEOUT = 10800
_active_proc = None
_proc_lock = threading.Lock()
# ---------------------------------------------------------------------------
# Inference
# ---------------------------------------------------------------------------
def generate(prompt, negative_prompt, resolution, seed):
global _active_proc
if not prompt or not prompt.strip():
raise gr.Error("Please enter a prompt.")
prompt = prompt.strip()[:500]
w, h = (int(x) for x in resolution.split("x"))
seed = int(seed or -1) if seed is not None else -1
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
output_path = f.name
cmd = [
"/app/sd-cli",
"--diffusion-model", DIFFUSION,
"--llm", LLM,
"--vae", VAE,
"-p", prompt,
"-n", negative_prompt or "",
"-W", str(w),
"-H", str(h),
"--steps", str(STEPS),
"--cfg-scale", str(CFG),
"--sampling-method", "euler_a",
"--schedule", "beta",
"-o", output_path,
"--diffusion-fa",
"--vae-tiling",
"-v",
]
if seed >= 0:
cmd += ["-s", str(seed)]
print(f"[gen] {w}x{h} steps={STEPS} seed={seed} prompt={prompt[:80]}")
t0 = time.time()
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
with _proc_lock:
_active_proc = proc
try:
stdout, stderr = proc.communicate(timeout=TIMEOUT)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
raise
elapsed = time.time() - t0
with _proc_lock:
_active_proc = None
if proc.returncode != 0:
err = stderr.decode(errors="replace")[-500:] if stderr else "Unknown error"
if proc.returncode == -9:
raise gr.Error("Out of memory (killed by OS). Try 512x512.")
raise gr.Error(f"sd-cli failed (code {proc.returncode}): {err}")
if not os.path.exists(output_path) or os.path.getsize(output_path) == 0:
raise gr.Error("No output image generated")
img = Image.open(output_path)
status = f"Generated in {elapsed:.1f}s ({w}x{h}, {STEPS} steps)"
print(f"[gen] {status}")
return img, status
except subprocess.TimeoutExpired:
with _proc_lock:
_active_proc = None
raise gr.Error(f"Generation timed out ({TIMEOUT//60} min limit)")
except gr.Error:
with _proc_lock:
_active_proc = None
raise
except Exception as e:
with _proc_lock:
_active_proc = None
raise gr.Error(f"Error: {e}")
# ---------------------------------------------------------------------------
# Gradio UI
# ---------------------------------------------------------------------------
with gr.Blocks(title="Z-Anime (CPU)") as demo:
gr.Markdown(
"**[Z-Anime 6B](https://huggingface.co/SeeSee21/Z-Anime)** S3-DiT Q5_0 GGUF "
"(distill 8-step) via [sd.cpp](https://github.com/leejet/stable-diffusion.cpp) | "
"Free CPU inference"
)
with gr.Row():
with gr.Column():
prompt_input = gr.Textbox(label="Prompt", lines=3,
placeholder="anime girl with silver hair, fantasy armor, dramatic lighting")
neg_input = gr.Textbox(label="Negative Prompt", lines=2,
value="lowres, bad anatomy, bad hands, text, error, worst quality, blurry")
with gr.Row():
res_input = gr.Dropdown(choices=RESOLUTIONS, value="512x512",
label="Resolution")
seed_input = gr.Number(value=-1, label="Seed (-1=random)", precision=0)
gen_btn = gr.Button("Generate (8 steps, CFG 1)", variant="primary", size="lg")
with gr.Column():
output_img = gr.Image(type="pil", label="Output")
status_box = gr.Textbox(label="Status", interactive=False)
gen_btn.click(fn=generate,
inputs=[prompt_input, neg_input, res_input, seed_input],
outputs=[output_img, status_box],
concurrency_limit=1)
def _on_unload():
with _proc_lock:
proc = _active_proc
if proc and proc.poll() is None:
print("[cleanup] User disconnected, killing sd-cli process")
proc.kill()
demo.unload(_on_unload)
demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)