Spaces:
Running on Zero
Running on Zero
Commit ·
a60dc1f
1
Parent(s): 3b21cc0
Move Flux2 download to startup (CPU) to avoid burning GPU time
Browse filesfrom_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>
- app.py +12 -7
- 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,
|
| 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
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 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(), "
|
| 201 |
if _flux_pipe is None:
|
| 202 |
-
_flux_pipe, _flux_device =
|
| 203 |
-
print("[flux]
|
| 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
|
| 47 |
-
"""Download and
|
| 48 |
import os
|
| 49 |
from diffusers import Flux2KleinPipeline
|
| 50 |
-
token
|
| 51 |
-
|
| 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 |
|