Matys BIECHE Claude Opus 4.8 (1M context) commited on
Commit ·
c04d9b2
1
Parent(s): f67d493
Speed up SDXL: LCM-LoRA (5 steps) + batched image generation
Browse filesReplace 18-step CFG=7 SDXL with LCM-LoRA scheduler (~5 steps, guidance 1.2)
on the same base model. Batch target+2 decoys into a single GPU call instead
of 4 sequential calls. ~4-6x faster asset generation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
app.py
CHANGED
|
@@ -52,9 +52,14 @@ llm = AutoModelForCausalLM.from_pretrained(
|
|
| 52 |
trust_remote_code=True,
|
| 53 |
)
|
| 54 |
|
| 55 |
-
from diffusers import StableDiffusionXLPipeline
|
| 56 |
|
| 57 |
IMAGE_MODEL_ID = "stabilityai/stable-diffusion-xl-base-1.0"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
pipe = StableDiffusionXLPipeline.from_pretrained(
|
| 60 |
IMAGE_MODEL_ID,
|
|
@@ -63,7 +68,12 @@ pipe = StableDiffusionXLPipeline.from_pretrained(
|
|
| 63 |
variant="fp16",
|
| 64 |
)
|
| 65 |
pipe.to("cuda")
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
|
| 69 |
# ---------------------------------------------------------------------------
|
|
@@ -199,55 +209,59 @@ def pil_to_b64(img: Image.Image) -> str:
|
|
| 199 |
return base64.b64encode(buffer.getvalue()).decode("utf-8")
|
| 200 |
|
| 201 |
|
| 202 |
-
def
|
| 203 |
-
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
|
|
|
|
|
|
|
|
|
| 207 |
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
-
|
| 219 |
-
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
|
| 222 |
def generate_assets(spec: VisualMiniGameSpec):
|
| 223 |
style = spec.visual_style
|
| 224 |
|
| 225 |
-
|
| 226 |
-
|
|
|
|
| 227 |
width=768,
|
| 228 |
height=512,
|
| 229 |
-
steps=18,
|
| 230 |
)
|
| 231 |
|
| 232 |
-
|
|
|
|
| 233 |
f"{spec.target_prompt}, {style}, isolated object, white background, no text",
|
| 234 |
-
width=512,
|
| 235 |
-
height=512,
|
| 236 |
-
steps=18,
|
| 237 |
-
)
|
| 238 |
-
|
| 239 |
-
decoy_1 = generate_image(
|
| 240 |
f"{spec.decoy_prompts[0]}, {style}, isolated object, white background, no text",
|
| 241 |
-
width=512,
|
| 242 |
-
height=512,
|
| 243 |
-
steps=18,
|
| 244 |
-
)
|
| 245 |
-
|
| 246 |
-
decoy_2 = generate_image(
|
| 247 |
f"{spec.decoy_prompts[1]}, {style}, isolated object, white background, no text",
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
)
|
| 252 |
|
| 253 |
return {
|
|
|
|
| 52 |
trust_remote_code=True,
|
| 53 |
)
|
| 54 |
|
| 55 |
+
from diffusers import StableDiffusionXLPipeline, LCMScheduler
|
| 56 |
|
| 57 |
IMAGE_MODEL_ID = "stabilityai/stable-diffusion-xl-base-1.0"
|
| 58 |
+
LCM_LORA_ID = "latent-consistency/lcm-lora-sdxl"
|
| 59 |
+
|
| 60 |
+
# Nombre de steps et guidance optimisés pour LCM (au lieu de 18 / 7.0 en SDXL classique)
|
| 61 |
+
IMAGE_STEPS = 5
|
| 62 |
+
IMAGE_GUIDANCE = 1.2
|
| 63 |
|
| 64 |
pipe = StableDiffusionXLPipeline.from_pretrained(
|
| 65 |
IMAGE_MODEL_ID,
|
|
|
|
| 68 |
variant="fp16",
|
| 69 |
)
|
| 70 |
pipe.to("cuda")
|
| 71 |
+
|
| 72 |
+
# LCM-LoRA : distillation par consistency model -> ~4-5 steps suffisent.
|
| 73 |
+
# On réutilise le même SDXL base (déjà téléchargé), on ajoute juste le LoRA + le scheduler LCM.
|
| 74 |
+
pipe.scheduler = LCMScheduler.from_config(pipe.scheduler.config)
|
| 75 |
+
pipe.load_lora_weights(LCM_LORA_ID)
|
| 76 |
+
pipe.fuse_lora()
|
| 77 |
|
| 78 |
|
| 79 |
# ---------------------------------------------------------------------------
|
|
|
|
| 209 |
return base64.b64encode(buffer.getvalue()).decode("utf-8")
|
| 210 |
|
| 211 |
|
| 212 |
+
def generate_images_batch(prompts, width, height, steps=IMAGE_STEPS):
|
| 213 |
+
"""Génère plusieurs images de MÊME taille en un seul appel GPU (batch).
|
| 214 |
|
| 215 |
+
Les prompts déjà en cache sont renvoyés directement ; seuls les manquants
|
| 216 |
+
sont générés, puis l'ordre d'origine est reconstruit.
|
| 217 |
+
"""
|
| 218 |
+
results = [None] * len(prompts)
|
| 219 |
+
todo_idx, todo_prompts = [], []
|
| 220 |
|
| 221 |
+
for i, p in enumerate(prompts):
|
| 222 |
+
key = (p, width, height, steps)
|
| 223 |
+
if key in ASSET_CACHE:
|
| 224 |
+
results[i] = ASSET_CACHE[key]
|
| 225 |
+
else:
|
| 226 |
+
todo_idx.append(i)
|
| 227 |
+
todo_prompts.append(p)
|
| 228 |
+
|
| 229 |
+
if todo_prompts:
|
| 230 |
+
with torch.no_grad():
|
| 231 |
+
out = pipe(
|
| 232 |
+
prompt=todo_prompts,
|
| 233 |
+
negative_prompt=[NEGATIVE_PROMPT] * len(todo_prompts),
|
| 234 |
+
width=width,
|
| 235 |
+
height=height,
|
| 236 |
+
num_inference_steps=steps,
|
| 237 |
+
guidance_scale=IMAGE_GUIDANCE,
|
| 238 |
+
).images
|
| 239 |
|
| 240 |
+
for slot, p, img in zip(todo_idx, todo_prompts, out):
|
| 241 |
+
ASSET_CACHE[(p, width, height, steps)] = img
|
| 242 |
+
results[slot] = img
|
| 243 |
+
|
| 244 |
+
return results
|
| 245 |
|
| 246 |
|
| 247 |
def generate_assets(spec: VisualMiniGameSpec):
|
| 248 |
style = spec.visual_style
|
| 249 |
|
| 250 |
+
# Background (768x512) -> 1 appel
|
| 251 |
+
(background,) = generate_images_batch(
|
| 252 |
+
[f"{spec.background_prompt}, {style}, no text, game background"],
|
| 253 |
width=768,
|
| 254 |
height=512,
|
|
|
|
| 255 |
)
|
| 256 |
|
| 257 |
+
# Target + 2 decoys (512x512, même taille) -> 1 seul appel batché
|
| 258 |
+
object_prompts = [
|
| 259 |
f"{spec.target_prompt}, {style}, isolated object, white background, no text",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
f"{spec.decoy_prompts[0]}, {style}, isolated object, white background, no text",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
f"{spec.decoy_prompts[1]}, {style}, isolated object, white background, no text",
|
| 262 |
+
]
|
| 263 |
+
target, decoy_1, decoy_2 = generate_images_batch(
|
| 264 |
+
object_prompts, width=512, height=512
|
| 265 |
)
|
| 266 |
|
| 267 |
return {
|