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 files

Replace 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>

Files changed (1) hide show
  1. app.py +50 -36
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
- pipe.enable_attention_slicing()
 
 
 
 
 
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 generate_image(prompt, width=512, height=512, steps=18):
203
- key = (prompt, width, height, steps)
204
 
205
- if key in ASSET_CACHE:
206
- return ASSET_CACHE[key]
 
 
 
207
 
208
- with torch.no_grad():
209
- image = pipe(
210
- prompt=prompt,
211
- negative_prompt=NEGATIVE_PROMPT,
212
- width=width,
213
- height=height,
214
- num_inference_steps=steps,
215
- guidance_scale=7.0,
216
- ).images[0]
 
 
 
 
 
 
 
 
 
217
 
218
- ASSET_CACHE[key] = image
219
- return image
 
 
 
220
 
221
 
222
  def generate_assets(spec: VisualMiniGameSpec):
223
  style = spec.visual_style
224
 
225
- background = generate_image(
226
- f"{spec.background_prompt}, {style}, no text, game background",
 
227
  width=768,
228
  height=512,
229
- steps=18,
230
  )
231
 
232
- target = generate_image(
 
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
- width=512,
249
- height=512,
250
- steps=18,
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 {