Files
hair/benchmark_9b_vs_4b.py
T
UbuntuandCursor 87ff2c15d0 chore: 精简生发提示词,去掉磨皮/美颜要求
统一改为「填充遮罩区域的头发」,涉及后端默认值、ComfyUI 工作流 JSON、
测试页、benchmark 脚本、local_test。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 15:57:57 +08:00

158 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""9B vs 4B 模型对比:3 张图 × 5 种发型 = 15 张对比图。
第一次运行:PLAN_TAG=A (9B 模型,需先切工作流到 .bak)
第二次运行:PLAN_TAG=Bplus4B 模型,需切回 4B 工作流)
"""
import json, os, time, base64, subprocess, urllib.request
from datetime import datetime
API_BASE = "http://127.0.0.1:8187"
TOKEN = "dev-shared-secret-2026"
IMAGES = [
"/home/ubuntu/hair/image/girl_img/girl1.jpg",
"/home/ubuntu/hair/image/girl_img/girl7.jpg",
"/home/ubuntu/hair/image/girl_img/girl13.jpg",
]
PLAN_TAG = os.getenv("PLAN_TAG", "A")
OUT_DIR = f"/home/ubuntu/hair/benchmark_out/9b_vs_4b_{PLAN_TAG}"
RESULT_JSON = os.path.join(OUT_DIR, "results.json")
REPORT_HTML = os.path.join(OUT_DIR, "report.html")
HAIR_STYLES = [(1, "椭圆"), (2, "花瓣"), (3, "心形"), (4, "直线"), (5, "波浪")]
WARMUP = os.getenv("WARMUP", "1") == "1" # 第一次调用做预热
os.makedirs(OUT_DIR, exist_ok=True)
def get_vram():
try:
out = subprocess.check_output(
["nvidia-smi", "--query-gpu=memory.used,memory.free",
"--format=csv,noheader,nounits"], text=True, timeout=5).strip()
used, free = out.split(",")
return int(used.strip()), int(free.strip())
except Exception:
return -1, -1
def _multipart(fields, files=None):
boundary = "----CmpTest" + str(int(time.time() * 1000))
parts = []
for k, v in fields.items():
parts.append(f"--{boundary}\r\n".encode())
parts.append(f'Content-Disposition: form-data; name="{k}"\r\n\r\n'.encode())
parts.append(str(v).encode())
parts.append(b"\r\n")
if files:
for field_name, (filename, data, mime) in files.items():
parts.append(f"--{boundary}\r\n".encode())
parts.append(f'Content-Disposition: form-data; name="{field_name}"; filename="{filename}"\r\n'.encode())
parts.append(f"Content-Type: {mime}\r\n\r\n".encode())
parts.append(data)
parts.append(b"\r\n")
parts.append(f"--{boundary}--\r\n".encode())
return b"".join(parts), boundary
def call_iface2(image_path, hair_style):
with open(image_path, "rb") as f:
img_data = f.read()
fields = {"gender": "female", "hair_style": str(hair_style),
"prompt": "填充遮罩区域的头发"}
body, boundary = _multipart(
fields, {"image_file": (os.path.basename(image_path), img_data, "image/jpeg")})
req = urllib.request.Request(f"{API_BASE}/api/v1/hair/grow", data=body, method="POST")
req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")
req.add_header("X-Internal-Token", TOKEN)
t0 = time.time()
try:
with urllib.request.urlopen(req, timeout=600) as resp:
data = json.loads(resp.read())
return data, time.time() - t0, None
except Exception as e:
return {}, time.time() - t0, str(e)
def extract_image(data, img_idx, hs):
"""从响应提取 grown_image_base64 并保存为 jpg。"""
if not data or not data.get("data"):
return None
items = data["data"].get("results") or []
if not items:
return None
b64 = items[0].get("grown_image_base64") or ""
if b64.startswith("data:"):
b64 = b64.split(",", 1)[1]
if not b64:
return None
fname = f"img{img_idx}_style{hs}.jpg"
path = os.path.join(OUT_DIR, fname)
with open(path, "wb") as f:
f.write(base64.b64decode(b64))
return path
def run_test():
results = []
print(f"9B vs 4B 对比测试 — Plan={PLAN_TAG}")
print(f"图片: {[os.path.basename(p) for p in IMAGES]}")
print(f"发型: {[(s,n) for s,n in HAIR_STYLES]}")
print(f"总调用: {len(IMAGES)*len(HAIR_STYLES)}\n")
# 预热请求(避免第一次冷启动计入统计)
if WARMUP:
print("[warmup] 预热请求 (girl13, style=1) ...")
t0 = time.time()
_, warmup_time, _ = call_iface2(IMAGES[2], 1)
print(f" warmup: {warmup_time:.1f}s\n")
for img_idx, img_path in enumerate(IMAGES, start=1):
img_name = os.path.basename(img_path)
for hs, hs_name in HAIR_STYLES:
print(f"[img{img_idx}/{len(IMAGES)}] {img_name} style={hs}({hs_name}) ...")
vram_before, _ = get_vram()
data, elapsed, error = call_iface2(img_path, hs)
vram_after, _ = get_vram()
code = data.get("code", -1) if data else -1
ok = (code == 0)
img_saved = extract_image(data, img_idx, hs) if ok else None
record = {
"plan": PLAN_TAG,
"image_idx": img_idx,
"image_name": img_name,
"image_path": img_path,
"hair_style": hs,
"hair_style_name": hs_name,
"timestamp": datetime.now().strftime("%H:%M:%S"),
"elapsed_s": round(elapsed, 2),
"success": ok,
"code": code,
"error": error,
"vram_before_mb": vram_before,
"vram_after_mb": vram_after,
"vram_delta_mb": vram_after - vram_before,
"saved_image_path": img_saved,
}
results.append(record)
status = "✓" if ok else "✗"
print(f" → {status} code={code} time={elapsed:.1f}s "
f"vram={vram_before}{vram_after}MB (Δ{vram_after-vram_before:+d}) "
f"img={'saved' if img_saved else 'none'}")
with open(RESULT_JSON, "w") as f:
json.dump(results, f, indent=2, ensure_ascii=False)
print(f"\n✅ 完成!{RESULT_JSON}")
print(f"📊 成功: {sum(1 for r in results if r['success'])}/{len(results)}")
if any(r['success'] for r in results):
avg = sum(r['elapsed_s'] for r in results if r['success']) / sum(1 for r in results if r['success'])
print(f"⏱ 平均: {avg:.2f}s")
if __name__ == "__main__":
run_test()