Nekochu commited on
Commit
e1ae826
·
1 Parent(s): 8852c51

switch to Docker with compiled sd.cpp

Browse files
Files changed (5) hide show
  1. Dockerfile +24 -0
  2. README.md +21 -15
  3. app.py +117 -135
  4. packages.txt +0 -3
  5. requirements.txt +0 -4
Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ RUN apt-get update && apt-get install -y --no-install-recommends \
4
+ git cmake build-essential libopenblas-dev && \
5
+ rm -rf /var/lib/apt/lists/*
6
+
7
+ # Build stable-diffusion.cpp from source (latest, with Anima support)
8
+ RUN git clone --depth 1 https://github.com/leejet/stable-diffusion.cpp /tmp/sdcpp && \
9
+ cd /tmp/sdcpp && mkdir build && cd build && \
10
+ cmake .. -DCMAKE_BUILD_TYPE=Release \
11
+ -DSD_BUILD_SHARED_LIBS=OFF \
12
+ -DGGML_OPENBLAS=ON && \
13
+ cmake --build . --config Release -j2 && \
14
+ cp bin/sd-cli /usr/local/bin/sd-cli && \
15
+ rm -rf /tmp/sdcpp
16
+
17
+ RUN pip install --no-cache-dir gradio Pillow huggingface-hub
18
+
19
+ WORKDIR /app
20
+ COPY app.py .
21
+ COPY README.md .
22
+
23
+ EXPOSE 7860
24
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,18 +1,24 @@
1
  ---
2
- title: Anima 2B Image Generation (CPU)
3
- emoji: "\U0001F3A8"
4
- colorFrom: blue
5
- colorTo: purple
6
- sdk: gradio
7
- sdk_version: 6.9.0
8
- app_file: app.py
9
  pinned: false
10
- license: mit
11
- python_version: "3.11"
12
- startup_duration_timeout: 1h
13
- preload_from_hub:
14
- - JusteLeo/Anima2-GGUF anima-preview2_q4_K_M.gguf
15
- - circlestone-labs/Anima split_files/text_encoders/qwen_3_06b_base.safetensors
16
- - circlestone-labs/Anima split_files/vae/qwen_image_vae.safetensors
17
- - Einhorn/Anima-Preview2-Turbo-LoRA anima_preview2_turbo_8step.safetensors
18
  ---
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Anima 2B CPU
3
+ emoji: 🎨
4
+ colorFrom: purple
5
+ colorTo: pink
6
+ sdk: docker
 
 
7
  pinned: false
8
+ license: other
9
+ short_description: Anime image generation with Anima 2B on CPU
10
+ tags:
11
+ - text-to-image
12
+ - anime
13
+ - gguf
14
+ - cpu
 
15
  ---
16
+
17
+ # Anima 2B Image Generation (CPU)
18
+
19
+ Generate anime images with Anima 2B (Q4_K_M GGUF) + Turbo LoRA on free CPU hardware.
20
+
21
+ - Engine: stable-diffusion.cpp (compiled from source with Anima support)
22
+ - Model: Anima 2B Q4_K_M (1.2 GB)
23
+ - Turbo LoRA: 8-step distillation (cfg 1.0)
24
+ - Hardware: CPU Basic
app.py CHANGED
@@ -1,157 +1,139 @@
1
- import os
2
- import shutil
3
- import time
4
- import gradio as gr
5
- from huggingface_hub import hf_hub_download
6
- from stable_diffusion_cpp import StableDiffusion
7
 
8
- # Download / locate model files
9
- print("Downloading model files...")
10
- t0 = time.time()
 
 
11
 
12
- diffusion_path = hf_hub_download(
13
- repo_id="JusteLeo/Anima2-GGUF",
14
- filename="anima-preview2_q4_K_M.gguf",
15
- )
16
- llm_path = hf_hub_download(
17
- repo_id="circlestone-labs/Anima",
18
- filename="split_files/text_encoders/qwen_3_06b_base.safetensors",
19
- )
20
- vae_path = hf_hub_download(
21
- repo_id="circlestone-labs/Anima",
22
- filename="split_files/vae/qwen_image_vae.safetensors",
23
- )
24
-
25
- # Download Turbo LoRA (8-step distillation)
26
- lora_src = hf_hub_download(
27
- repo_id="Einhorn/Anima-Preview2-Turbo-LoRA",
28
- filename="anima_preview2_turbo_8step.safetensors",
29
- )
30
- # Copy LoRA to a flat directory for sd.cpp lora_model_dir
31
  LORA_DIR = "/tmp/loras"
 
32
  os.makedirs(LORA_DIR, exist_ok=True)
33
- lora_dest = os.path.join(LORA_DIR, "anima_turbo_8step.safetensors")
34
- if not os.path.exists(lora_dest):
35
- shutil.copy2(lora_src, lora_dest)
36
- print(f"LoRA copied to {lora_dest}")
37
 
38
- print(f"Model files ready in {time.time() - t0:.1f}s")
 
 
 
 
 
 
 
 
 
 
39
 
40
- # Load model
41
- print("Loading Anima 2B model with Turbo LoRA...")
42
  t0 = time.time()
43
- sd = StableDiffusion(
44
- diffusion_model_path=diffusion_path,
45
- llm_path=llm_path,
46
- vae_path=vae_path,
47
- lora_model_dir=LORA_DIR,
48
- diffusion_flash_attn=True,
49
- n_threads=2,
50
- verbose=True,
51
- )
52
- print(f"Model loaded in {time.time() - t0:.1f}s")
53
-
54
- RESOLUTIONS = [
55
- "1024x1024",
56
- "768x768",
57
- "512x512",
58
- "1024x768",
59
- "768x1024",
60
- "1280x768",
61
- "768x1280",
62
- ]
63
-
64
-
65
- def generate(
66
- prompt: str,
67
- resolution: str,
68
- steps: int,
69
- cfg_scale: float,
70
- seed: int,
71
- ) -> tuple:
72
- """Generate an image from a text prompt using Anima 2B with Turbo LoRA."""
73
- if not prompt or not prompt.strip():
74
- raise gr.Error("Please enter a prompt.")
75
 
76
- w, h = (int(x) for x in resolution.split("x"))
 
 
 
 
77
 
78
- # Prepend LoRA trigger to prompt
79
- full_prompt = f"<lora:anima_turbo_8step:1.0> {prompt}"
80
 
81
- t0 = time.time()
82
- images = sd.generate_image(
83
- prompt=full_prompt,
84
- width=w,
85
- height=h,
86
- sample_steps=steps,
87
- cfg_scale=cfg_scale,
88
- seed=seed,
89
- vae_tiling=True,
90
- )
91
- elapsed = time.time() - t0
92
 
93
- if not images or images[0] is None:
94
- raise gr.Error("Generation failed. Try a different prompt or settings.")
95
-
96
- return images[0], f"Generated {resolution} in {elapsed:.1f}s ({steps} steps, cfg {cfg_scale})"
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
- with gr.Blocks(title="Anima 2B Image Generation (CPU)") as demo:
100
- gr.Markdown("# Anima 2B Image Generation (CPU)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  gr.Markdown(
102
- "Generate images using [Anima 2B](https://huggingface.co/circlestone-labs/Anima) "
103
- "via stable-diffusion.cpp on free CPU hardware. "
104
- "Q4_K_M GGUF (~1.2 GB) with "
105
- "[Turbo LoRA](https://huggingface.co/Einhorn/Anima-Preview2-Turbo-LoRA) "
106
- "for 8-step generation."
107
  )
108
-
109
  with gr.Row():
110
  with gr.Column():
111
- prompt_box = gr.Textbox(
112
- label="Prompt",
113
- placeholder="anime girl with silver hair, fantasy armor",
114
- lines=3,
115
- )
116
- resolution_dd = gr.Dropdown(
117
- label="Resolution",
118
- choices=RESOLUTIONS,
119
- value="1024x1024",
120
- )
121
  with gr.Row():
122
- steps_slider = gr.Slider(
123
- label="Steps",
124
- minimum=1,
125
- maximum=50,
126
- step=1,
127
- value=8,
128
- )
129
- cfg_slider = gr.Slider(
130
- label="CFG Scale",
131
- minimum=1.0,
132
- maximum=10.0,
133
- step=0.5,
134
- value=1.0,
135
- )
136
- seed_number = gr.Number(
137
- label="Seed (-1 = random)",
138
- value=-1,
139
- precision=0,
140
- )
141
- run_btn = gr.Button("Generate", variant="primary")
142
-
143
- gr.Markdown(
144
- "**Turbo LoRA active:** Defaults are Steps=8, CFG=1.0. "
145
- "For non-turbo (no LoRA), use Steps=30, CFG=4.0."
146
- )
147
  with gr.Column():
148
- output_image = gr.Image(label="Result", type="pil")
149
- timing_label = gr.Textbox(label="Timing", interactive=False)
150
 
151
- inputs = [prompt_box, resolution_dd, steps_slider, cfg_slider, seed_number]
152
- outputs = [output_image, timing_label]
 
153
 
154
- run_btn.click(fn=generate, inputs=inputs, outputs=outputs, api_name="generate")
155
- prompt_box.submit(fn=generate, inputs=inputs, outputs=outputs, api_name=False)
 
156
 
157
- demo.launch(show_error=True, ssr_mode=False, theme="NoCrypt/miku")
 
1
+ """Anima 2B Image Generation (CPU) via sd-cli binary"""
 
 
 
 
 
2
 
3
+ import os, time, subprocess, tempfile, shutil
4
+ from pathlib import Path
5
+ from PIL import Image
6
+ from huggingface_hub import hf_hub_download
7
+ import gradio as gr
8
 
9
+ # ---------------------------------------------------------------------------
10
+ # Download models
11
+ # ---------------------------------------------------------------------------
12
+ MODELS_DIR = "/tmp/anima_models"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  LORA_DIR = "/tmp/loras"
14
+ os.makedirs(MODELS_DIR, exist_ok=True)
15
  os.makedirs(LORA_DIR, exist_ok=True)
 
 
 
 
16
 
17
+ def ensure_model(repo_id, filename, subdir=""):
18
+ path = os.path.join(MODELS_DIR, filename)
19
+ if os.path.exists(path):
20
+ return path
21
+ print(f"[init] Downloading {repo_id}/{subdir}/{filename}...")
22
+ src = hf_hub_download(
23
+ repo_id=repo_id,
24
+ filename=f"{subdir}/{filename}" if subdir else filename,
25
+ )
26
+ shutil.copy2(src, path)
27
+ return path
28
 
29
+ print("[init] Ensuring model files...")
 
30
  t0 = time.time()
31
+ diffusion_path = ensure_model("JusteLeo/Anima2-GGUF", "anima-preview2_q4_K_M.gguf")
32
+ llm_path = ensure_model("circlestone-labs/Anima", "qwen_3_06b_base.safetensors", "split_files/text_encoders")
33
+ vae_path = ensure_model("circlestone-labs/Anima", "qwen_image_vae.safetensors", "split_files/vae")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ # Turbo LoRA (8-step)
36
+ lora_src = hf_hub_download("Einhorn/Anima-Preview2-Turbo-LoRA", "anima_preview2_turbo_8step.safetensors")
37
+ lora_path = os.path.join(LORA_DIR, "anima_turbo_8step.safetensors")
38
+ if not os.path.exists(lora_path):
39
+ shutil.copy2(lora_src, lora_path)
40
 
41
+ print(f"[init] Models ready in {time.time()-t0:.1f}s")
 
42
 
43
+ # ---------------------------------------------------------------------------
44
+ # Inference via sd-cli binary
45
+ # ---------------------------------------------------------------------------
46
+ RESOLUTIONS = ["512x512", "768x768", "1024x1024", "1024x768", "768x1024"]
 
 
 
 
 
 
 
47
 
48
+ def generate(prompt, resolution, steps, cfg_scale, seed):
49
+ if not prompt or not prompt.strip():
50
+ raise gr.Error("Please enter a prompt.")
 
51
 
52
+ w, h = (int(x) for x in resolution.split("x"))
53
+ seed = int(seed) if int(seed) >= 0 else -1
54
+
55
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
56
+ output_path = f.name
57
+
58
+ # Build sd-cli command (same as official docs)
59
+ cmd = [
60
+ "sd-cli",
61
+ "--diffusion-model", diffusion_path,
62
+ "--llm", llm_path,
63
+ "--vae", vae_path,
64
+ "--lora-model-dir", LORA_DIR,
65
+ "-p", f"<lora:anima_turbo_8step:1.0> {prompt}",
66
+ "-W", str(w),
67
+ "-H", str(h),
68
+ "--steps", str(int(steps)),
69
+ "--cfg-scale", str(float(cfg_scale)),
70
+ "--sampling-method", "euler",
71
+ "-o", output_path,
72
+ "--diffusion-fa",
73
+ "--vae-tiling",
74
+ "-v",
75
+ ]
76
+ if seed >= 0:
77
+ cmd += ["-s", str(seed)]
78
+
79
+ print(f"[gen] {w}x{h} steps={steps} cfg={cfg_scale} seed={seed}")
80
+ t0 = time.time()
81
 
82
+ try:
83
+ result = subprocess.run(
84
+ cmd, capture_output=True, text=True, timeout=1800,
85
+ )
86
+ elapsed = time.time() - t0
87
+
88
+ if result.returncode != 0:
89
+ err = result.stderr[-500:] if result.stderr else "Unknown error"
90
+ raise gr.Error(f"sd-cli failed (code {result.returncode}): {err}")
91
+
92
+ if not os.path.exists(output_path) or os.path.getsize(output_path) == 0:
93
+ raise gr.Error("No output image generated")
94
+
95
+ img = Image.open(output_path)
96
+ status = f"Generated in {elapsed:.1f}s ({w}x{h}, {steps} steps, cfg {cfg_scale})"
97
+ print(f"[gen] {status}")
98
+ return img, status
99
+
100
+ except subprocess.TimeoutExpired:
101
+ raise gr.Error("Generation timed out (30 min limit)")
102
+ except gr.Error:
103
+ raise
104
+ except Exception as e:
105
+ raise gr.Error(f"Error: {e}")
106
+
107
+ # ---------------------------------------------------------------------------
108
+ # Gradio UI
109
+ # ---------------------------------------------------------------------------
110
+ with gr.Blocks(title="Anima 2B (CPU)") as demo:
111
  gr.Markdown(
112
+ "# Anima 2B Image Generation (CPU)\n"
113
+ "Generate anime images using [Anima 2B](https://huggingface.co/circlestone-labs/Anima) "
114
+ "with [Turbo LoRA](https://huggingface.co/Einhorn/Anima-Preview2-Turbo-LoRA) (8 steps). "
115
+ "Powered by [sd.cpp](https://github.com/leejet/stable-diffusion.cpp)."
 
116
  )
 
117
  with gr.Row():
118
  with gr.Column():
119
+ prompt_input = gr.Textbox(label="Prompt", lines=3,
120
+ placeholder="anime girl with silver hair, fantasy armor, dramatic lighting")
121
+ res_input = gr.Dropdown(choices=RESOLUTIONS, value="512x512", label="Resolution")
 
 
 
 
 
 
 
122
  with gr.Row():
123
+ steps_input = gr.Slider(minimum=4, maximum=30, value=8, step=1, label="Steps")
124
+ cfg_input = gr.Slider(minimum=1.0, maximum=10.0, value=1.0, step=0.5, label="CFG Scale")
125
+ seed_input = gr.Number(value=-1, label="Seed", precision=0)
126
+ gen_btn = gr.Button("Generate", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  with gr.Column():
128
+ output_img = gr.Image(type="pil", label="Output")
129
+ status_box = gr.Textbox(label="Status", interactive=False)
130
 
131
+ gen_btn.click(fn=generate,
132
+ inputs=[prompt_input, res_input, steps_input, cfg_input, seed_input],
133
+ outputs=[output_img, status_box])
134
 
135
+ gr.Markdown("---\nAnima 2B Q4_K_M GGUF + Turbo LoRA (8 steps) | "
136
+ "[Model](https://huggingface.co/circlestone-labs/Anima) | "
137
+ "[sd.cpp](https://github.com/leejet/stable-diffusion.cpp)")
138
 
139
+ demo.launch(server_name="0.0.0.0", port=7860, show_error=True, theme="NoCrypt/miku")
packages.txt DELETED
@@ -1,3 +0,0 @@
1
- build-essential
2
- cmake
3
- libopenblas-dev
 
 
 
 
requirements.txt DELETED
@@ -1,4 +0,0 @@
1
- git+https://github.com/william-murray1204/stable-diffusion-cpp-python.git
2
- gradio
3
- Pillow
4
- huggingface-hub