"""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)