Spaces:
Runtime error
Runtime error
File size: 8,359 Bytes
c24437c e26a2a9 039eed5 443e0c8 51dd78a e26a2a9 74b81b3 51dd78a 74b81b3 443e0c8 74b81b3 51dd78a c80999d 4d60c66 c80999d c24437c 66686bd c24437c c80999d e26a2a9 24dca9f e26a2a9 74b81b3 51dd78a c80999d 74b81b3 51dd78a 74b81b3 51dd78a 74b81b3 51dd78a e26a2a9 24dca9f 66686bd 24dca9f e26a2a9 24dca9f 74b81b3 51dd78a 74b81b3 51dd78a 74b81b3 51dd78a 74b81b3 51dd78a 74b81b3 c80999d 74b81b3 51dd78a 74b81b3 51dd78a 74b81b3 c80999d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
import os
import subprocess
import sys
import spaces
import torch
from optimization import optimize_pipeline_
from diffusers import QwenImageEditPlusPipeline
from PIL import Image
import gradio as gr
HF_BASE_MODEL = "Qwen/Qwen-Image-Edit-2509"
BFS_LORA = "Alissonerdx/BFS-Best-Face-Swap"
BFS_LORA_WEIGHT = "bfs_head_v3_qwen_image_edit_2509.safetensors" # Head V3 (recommended)
# --------- PIPELINE (ZERO GPU) ---------
# Lưu device để dùng lại trong quá trình suy luận.
EXEC_DEVICE = "cpu"
# Cho phép ép dùng CPU nếu GPU yếu hoặc hay abort (ZeroGPU không ổn định).
FORCE_CPU = bool(int(os.getenv("FORCE_CPU", "1")))
# Chỉ bật GPU khi thực sự muốn (mặc định = 0 để tránh abort vì OOM trên ZeroGPU).
PREFER_GPU = bool(int(os.getenv("PREFER_GPU", "0")))
def ensure_torchvision():
"""
Qwen2VLProcessor yêu cầu torchvision. Thử import, nếu thiếu sẽ cài đặt phiên bản khớp torch.
"""
try:
import torchvision # noqa: F401
return
except ImportError:
torch_version = torch.__version__.split("+")[0]
try:
subprocess.check_call(
[sys.executable, "-m", "pip", "install", f"torchvision=={torch_version}"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
import torchvision # noqa: F401
except Exception as exc: # pragma: no cover - chỉ chạy trên hạ tầng Spaces
raise ImportError(
"Torchvision is required for Qwen2VLProcessor. "
"Please add a matching torchvision to requirements (e.g. pip install torchvision==torch_version)."
) from exc
def _build_pipeline(device: str, dtype: torch.dtype):
ensure_torchvision()
pipe = QwenImageEditPlusPipeline.from_pretrained(
HF_BASE_MODEL,
torch_dtype=dtype,
)
pipe.to(device)
# giảm chiếm dụng VRAM/RAM
pipe.enable_attention_slicing()
pipe.enable_vae_slicing()
# load LoRA BFS Head V3
pipe.load_lora_weights(
BFS_LORA,
weight_name=BFS_LORA_WEIGHT,
adapter_name="bfs_head_v3",
)
pipe.set_adapters(["bfs_head_v3"], adapter_weights=[1.0])
pipe.set_progress_bar_config(disable=True)
return pipe
def maybe_optimize_pipeline(pipe):
"""
Áp dụng AOTI tối ưu hóa trên GPU (nếu đang chạy CUDA).
Dùng input dummy nhỏ để tránh tốn VRAM, fallback im lặng nếu lỗi.
"""
if EXEC_DEVICE != "cuda":
return pipe
try:
dummy = Image.new("RGB", (256, 256))
generator = torch.Generator(device="cuda").manual_seed(0)
optimize_pipeline_(
pipe,
image=[dummy, dummy],
prompt="warmup",
negative_prompt=" ",
num_inference_steps=1,
true_cfg_scale=1.0,
guidance_scale=1.0,
num_images_per_prompt=1,
generator=generator,
width=256,
height=256,
)
except Exception:
# Nếu tối ưu thất bại (thường do bộ nhớ), giữ pipeline gốc để tiếp tục chạy.
pass
return pipe
@spaces.GPU # bắt buộc cho ZeroGPU
def load_pipeline():
global EXEC_DEVICE
# Mặc định chạy CPU để tránh GPU abort. Bật GPU bằng PREFER_GPU=1 và FORCE_CPU=0.
prefer_cuda = torch.cuda.is_available() and PREFER_GPU and not FORCE_CPU
device = "cuda" if prefer_cuda else "cpu"
dtype = torch.float16 if device == "cuda" else torch.float32
try:
pipe = _build_pipeline(device, dtype)
EXEC_DEVICE = device
pipe = maybe_optimize_pipeline(pipe)
return pipe
except Exception as exc:
# GPU worker thường abort vì OOM. Fallback CPU để không crash app.
if device == "cuda":
device = "cpu"
dtype = torch.float32
pipe = _build_pipeline(device, dtype)
EXEC_DEVICE = device
return pipe
raise exc
pipe = load_pipeline()
# --------- UTILITIES ---------
def resize_to_max(img: Image.Image, max_side: int = 896) -> Image.Image:
w, h = img.size
max_dim = max(w, h)
if max_dim <= max_side:
return img # không upscale
scale = max_side / max_dim
new_w = int(w * scale)
new_h = int(h * scale)
return img.resize((new_w, new_h), Image.Resampling.LANCZOS)
DEFAULT_PROMPT = (
"head_swap: start with Picture 1 as the base image, keeping its lighting, "
"environment, and background. remove the head from Picture 1 completely and "
"replace it with the head from Picture 2. ensure the head and body have correct "
"anatomical proportions, and blend the skin tones, shadows, and lighting naturally "
"so the final result appears as one coherent, realistic person."
)
# --------- INFERENCE FUNCTION ---------
def run_bfs(
body_image, # Picture 1 (body)
face_image, # Picture 2 (face)
prompt_text,
steps,
true_cfg_scale,
guidance_scale,
seed,
):
if body_image is None or face_image is None:
return None, "⚠️ Cần upload đủ 2 ảnh: Picture 1 (body) và Picture 2 (face)."
# BFS Head V3: Image 1 = body, Image 2 = face
body_image = resize_to_max(body_image)
face_image = resize_to_max(face_image)
if not str(prompt_text).strip():
prompt = DEFAULT_PROMPT
else:
prompt = prompt_text.strip()
generator = torch.Generator(device=EXEC_DEVICE).manual_seed(int(seed))
inputs = {
"image": [body_image, face_image], # rất quan trọng: [body, face]
"prompt": prompt,
"negative_prompt": " ",
"num_inference_steps": int(steps),
"true_cfg_scale": float(true_cfg_scale),
"guidance_scale": float(guidance_scale),
"num_images_per_prompt": 1,
"generator": generator,
"width": body_image.width,
"height": body_image.height,
}
with torch.inference_mode():
out = pipe(**inputs)
return out.images[0], ""
# --------- GRADIO UI ---------
with gr.Blocks(title="BFS - Best Face Swap (Qwen Image Edit 2509, CPU)") as demo:
gr.Markdown(
"""
# 🧠 BFS - Best Face Swap (Qwen Image Edit 2509, CPU)
**BFS Head V3** – Picture 1 = **Body**, Picture 2 = **Face**.
Model chạy trên **CPU (zero GPU)** nên sẽ hơi chậm, ưu tiên ảnh vừa phải (≤ 896px cạnh dài).
> Vui lòng không dùng cho người thật / người nổi tiếng ngoài đời.
"""
)
with gr.Row():
with gr.Column():
body_image = gr.Image(
label="Picture 1 - BODY (ảnh gốc, giữ background)",
type="pil",
)
face_image = gr.Image(
label="Picture 2 - FACE (ảnh mặt muốn ghép)",
type="pil",
)
prompt_box = gr.Textbox(
label="Prompt (để trống dùng prompt BFS Head V3 mặc định)",
value="",
lines=4,
)
steps = gr.Slider(
label="Steps",
minimum=8,
maximum=40,
value=24,
step=1,
)
true_cfg_scale = gr.Slider(
label="True CFG Scale",
minimum=0.0,
maximum=10.0,
value=4.0,
step=0.1,
)
guidance_scale = gr.Slider(
label="Guidance Scale",
minimum=0.0,
maximum=8.0,
value=1.0,
step=0.1,
)
seed = gr.Number(
label="Seed",
value=0,
precision=0,
)
run_button = gr.Button("🚀 Run Face / Head Swap", variant="primary")
with gr.Column():
output_image = gr.Image(
label="Kết quả",
type="pil",
)
info = gr.Markdown("")
run_button.click(
fn=run_bfs,
inputs=[body_image, face_image, prompt_box, steps, true_cfg_scale, guidance_scale, seed],
outputs=[output_image, info],
)
if __name__ == "__main__":
demo.launch()
|