#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""把 benchmark_out/matrix/results.json 生成 HTML 报告。
每个组合一行:原图 + 3次生发图 + 耗时/显存。
图片用 base64 内嵌(自包含单文件,便于部署)。
"""
import base64
import json
import os
from pathlib import Path
OUT = Path("/home/ubuntu/hair/benchmark_out/matrix")
RESULTS = OUT / "results.json"
HTML = OUT / "report.html"
RES_LABEL = {"orig": "原图", "640": "640", "896": "896(默认)"}
MODEL_LABEL = {
"4b-fp8": "4B fp8 (3.8G)",
"9b-fp8": "9B fp8 (8.8G)",
"9b-Q5": "9B Q5_K_M (6.6G)",
"9b-Q4": "9B Q4_K_M (5.6G)",
}
MODEL_ORDER = ["4b-fp8", "9b-Q4", "9b-Q5", "9b-fp8"]
def img_b64(path, max_w=320, quality=72):
"""读图片,缩放到 max_w 宽、JPEG 降质后转 base64 data URI(压缩体积便于入 git)。"""
if not path or not os.path.isfile(path):
return None
from io import BytesIO
from PIL import Image
im = Image.open(path).convert("RGB")
if im.width > max_w:
nh = round(im.height * max_w / im.width)
im = im.resize((max_w, nh), Image.LANCZOS)
buf = BytesIO()
im.save(buf, format="JPEG", quality=quality, optimize=True)
b = base64.b64encode(buf.getvalue()).decode()
return f"data:image/jpeg;base64,{b}"
def thumb(data_uri, alt="", cls=""):
if not data_uri:
return f'
⚠ 失败
'
return f'
'
def main():
data = json.load(open(RESULTS, encoding="utf-8"))
# 预读原图 base64(3 张图)
orig_b64 = {}
for c in data:
ip = c["img_path"]
if c["img"] not in orig_b64:
orig_b64[c["img"]] = img_b64(ip, 200)
# 统计:每个模型的平均耗时、平均峰值显存
stats = {}
for c in data:
m = c["model"]
stats.setdefault(m, {"times": [], "peaks": []})
for r in c["runs"]:
if not r["error"]:
stats[m]["times"].append(r["elapsed"])
stats[m]["peaks"].append(r["gpu_peak"])
rows_html = []
# 按模型顺序、分辨率顺序、图片顺序排列
for m in MODEL_ORDER:
mdata = [c for c in data if c["model"] == m]
for rlabel in ["orig", "640", "896"]:
for ilabel in ["asdf", "qwer", "girl5"]:
c = next((x for x in mdata if x["res"] == rlabel and x["img"] == ilabel), None)
if not c:
continue
# 3 次结果图
run_cells = []
for i, r in enumerate(c["runs"]):
uri = img_b64(r["grown_path"]) if not r["error"] else None
if uri:
run_cells.append(
f'第{i+1}次 · {r["elapsed"]:.1f}s
'
f'{thumb(uri, f"r{i+1}", "result-img")}
'
)
else:
run_cells.append(
f'第{i+1}次 · 失败
'
f'
⚠ {r["error"][:30] if r["error"] else ""}
'
)
orig = orig_b64.get(c["img"])
rows_html.append(f'''
{MODEL_LABEL.get(m, m)}
res={RES_LABEL.get(rlabel, rlabel)}
{thumb(orig, "原图", "orig-img")}
{ilabel}
{"".join(run_cells)}
''')
# 模型对比汇总
summary_rows = []
for m in MODEL_ORDER:
s = stats.get(m, {"times": [], "peaks": []})
if s["times"]:
avg_t = sum(s["times"]) / len(s["times"])
max_p = max(s["peaks"]) / 1024
summary_rows.append(
f"| {MODEL_LABEL.get(m,m)} | {avg_t:.1f}s | "
f"{max_p:.1f} GB | {len(s['times'])} 成功 |
"
)
html = f"""
Flux 模型矩阵测试报告 — 接口2女性花瓣形
💇 Flux 模型矩阵测试报告
接口2女性 · 花瓣形发型 · 4模型 × 3分辨率 × 3图 × 3次 = 108 次 · RTX 3090 24GB
📊 模型对比汇总
| 模型 | 平均耗时 | 峰值显存 | 成功次数 |
{"".join(summary_rows)}
🖼️ 各组合对比(每行:原图 + 3次生发结果)
{"".join(rows_html)}
"""
with open(HTML, "w", encoding="utf-8") as f:
f.write(html)
print(f"✓ 报告已生成: {HTML} ({HTML.stat().st_size//1024} KB)")
if __name__ == "__main__":
main()