someone-in-the-world Claude Sonnet 4.6 commited on
Commit
a60dc1f
·
1 Parent(s): 3b21cc0

Move Flux2 download to startup (CPU) to avoid burning GPU time

Browse files

from_pretrained + load_lora_weights now run at module level on CPU —
no GPU seconds consumed. Inside @spaces.GPU, only pipe.to("cuda")
runs (fast memory transfer). GPU duration reduced from 300s to 120s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +12 -7
  2. pipeline_flux.py +13 -7
app.py CHANGED
@@ -17,7 +17,7 @@ from image_utils import _b64_to_pil, _collect_source_pils
17
  from validators import _validate_target, _validate_selection, _validate_sources
18
  from logging_utils import spawn_log
19
  from pipeline import detect as _pipeline_detect, run_swap, MAX_FACES
20
- from pipeline_flux import run_swap_flux, load_flux_pipeline, MAX_FLUX_FACES
21
 
22
  # basicsr (gfpgan dep) imports this submodule which was removed in torchvision>=0.17
23
  import sys
@@ -103,8 +103,13 @@ _cpu_face_analyzer = FaceAnalysis(name="buffalo_l",
103
  _cpu_face_analyzer.prepare(ctx_id=0, det_size=(640, 640))
104
  print("[startup] CPU FaceAnalysis ready.", flush=True)
105
 
106
- # ── Flux2 pipeline (lazy — loaded on first Quality request inside @spaces.GPU) ─
107
- _flux_pipe = None
 
 
 
 
 
108
  _flux_device = None
109
 
110
 
@@ -182,7 +187,7 @@ def swap_faces(target_b64, selected_json, sources_json):
182
  print("[swap] ===== END =====", flush=True)
183
 
184
 
185
- @spaces.GPU(duration=300)
186
  def swap_faces_quality(target_b64, selected_json, sources_json):
187
  """Flux2 Quality swap: per-face head crop → Flux2 → paste back. Generator: yields (image, status)."""
188
  global _flux_pipe, _flux_device
@@ -197,10 +202,10 @@ def swap_faces_quality(target_b64, selected_json, sources_json):
197
 
198
  target_pil = _b64_to_pil(target_b64)
199
 
200
- yield gr.update(), "Loading Quality model…"
201
  if _flux_pipe is None:
202
- _flux_pipe, _flux_device = load_flux_pipeline()
203
- print("[flux] model loaded", flush=True)
204
 
205
  yield gr.update(), "Swapping faces (Quality)…"
206
  try:
 
17
  from validators import _validate_target, _validate_selection, _validate_sources
18
  from logging_utils import spawn_log
19
  from pipeline import detect as _pipeline_detect, run_swap, MAX_FACES
20
+ from pipeline_flux import run_swap_flux, load_flux_pipeline_cpu, move_flux_to_device, MAX_FLUX_FACES
21
 
22
  # basicsr (gfpgan dep) imports this submodule which was removed in torchvision>=0.17
23
  import sys
 
103
  _cpu_face_analyzer.prepare(ctx_id=0, det_size=(640, 640))
104
  print("[startup] CPU FaceAnalysis ready.", flush=True)
105
 
106
+ # ── Flux2 pipeline: weights downloaded on CPU at startup (no GPU cost),
107
+ # then moved to CUDA inside @spaces.GPU on the first Quality request. ──────
108
+ print("[startup] loading Flux2 pipeline on CPU (download may take a while)…", flush=True)
109
+ _flux_pipe_cpu = load_flux_pipeline_cpu()
110
+ print("[startup] Flux2 CPU pipeline ready.", flush=True)
111
+
112
+ _flux_pipe = None # set to the CUDA pipe on first Quality call
113
  _flux_device = None
114
 
115
 
 
187
  print("[swap] ===== END =====", flush=True)
188
 
189
 
190
+ @spaces.GPU(duration=120)
191
  def swap_faces_quality(target_b64, selected_json, sources_json):
192
  """Flux2 Quality swap: per-face head crop → Flux2 → paste back. Generator: yields (image, status)."""
193
  global _flux_pipe, _flux_device
 
202
 
203
  target_pil = _b64_to_pil(target_b64)
204
 
205
+ yield gr.update(), "Moving Quality model to GPU…"
206
  if _flux_pipe is None:
207
+ _flux_pipe, _flux_device = move_flux_to_device(_flux_pipe_cpu)
208
+ print("[flux] pipeline on GPU ready", flush=True)
209
 
210
  yield gr.update(), "Swapping faces (Quality)…"
211
  try:
pipeline_flux.py CHANGED
@@ -43,25 +43,31 @@ def _resize_for_flux(pil_img):
43
  return pil_img.resize((new_w, new_h), Image.LANCZOS), (w, h)
44
 
45
 
46
- def load_flux_pipeline():
47
- """Download and initialise the FLUX.2 Klein pipeline with the face-swap LoRA."""
48
  import os
49
  from diffusers import Flux2KleinPipeline
50
- token = os.environ.get("HF_TOKEN") or None
51
- device = "cuda" if torch.cuda.is_available() else "cpu"
52
- print(f"[flux] loading pipeline on {device}…", flush=True)
53
  pipe = Flux2KleinPipeline.from_pretrained(
54
  "black-forest-labs/FLUX.2-klein-9B",
55
  torch_dtype=torch.bfloat16,
56
  token=token,
57
  )
58
- pipe.to(device)
59
  pipe.load_lora_weights(
60
  "Alissonerdx/BFS-Best-Face-Swap",
61
  weight_name="bfs_head_v1_flux-klein_9b_step3750_rank64.safetensors",
62
  token=token,
63
  )
64
- print("[flux] pipeline ready.", flush=True)
 
 
 
 
 
 
 
 
65
  return pipe, device
66
 
67
 
 
43
  return pil_img.resize((new_w, new_h), Image.LANCZOS), (w, h)
44
 
45
 
46
+ def load_flux_pipeline_cpu():
47
+ """Download weights and load onto CPU — safe to call at startup outside @spaces.GPU."""
48
  import os
49
  from diffusers import Flux2KleinPipeline
50
+ token = os.environ.get("HF_TOKEN") or None
51
+ print("[flux] downloading / loading pipeline on CPU…", flush=True)
 
52
  pipe = Flux2KleinPipeline.from_pretrained(
53
  "black-forest-labs/FLUX.2-klein-9B",
54
  torch_dtype=torch.bfloat16,
55
  token=token,
56
  )
 
57
  pipe.load_lora_weights(
58
  "Alissonerdx/BFS-Best-Face-Swap",
59
  weight_name="bfs_head_v1_flux-klein_9b_step3750_rank64.safetensors",
60
  token=token,
61
  )
62
+ print("[flux] pipeline on CPU ready.", flush=True)
63
+ return pipe
64
+
65
+
66
+ def move_flux_to_device(pipe):
67
+ """Move the CPU-loaded pipeline to the available device. Call inside @spaces.GPU."""
68
+ device = "cuda" if torch.cuda.is_available() else "cpu"
69
+ print(f"[flux] moving pipeline to {device}…", flush=True)
70
+ pipe.to(device)
71
  return pipe, device
72
 
73