- 测试结果图(258张)和原图(3张)以JPG存入 static/bench/{matrix,hairstyle,orig}/
- 报告HTML改为 <img src> 引用相对路径,体积从 ~45MB 降至 ~30KB
- 报告和图片均部署在 static/ 下,可通过URL直接访问
158 lines
6.5 KiB
Python
158 lines
6.5 KiB
Python
#!/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_src(path):
|
||
"""把绝对路径转成报告里的相对 URL(报告在 static/,图片在 static/bench/)。"""
|
||
if not path:
|
||
return None
|
||
p = str(path)
|
||
# benchmark_out/matrix/xxx.jpg -> bench/matrix/xxx.jpg
|
||
if "benchmark_out/matrix/" in p:
|
||
return "bench/matrix/" + os.path.basename(p)
|
||
if "benchmark_out/hairstyle/" in p:
|
||
return "bench/hairstyle/" + os.path.basename(p)
|
||
return None
|
||
|
||
|
||
def thumb(src, alt="", cls=""):
|
||
if not src:
|
||
return f'<div class="na {cls}">⚠ 失败</div>'
|
||
return f'<img class="{cls}" src="{src}" alt="{alt}" loading="lazy">'
|
||
|
||
|
||
def main():
|
||
data = json.load(open(RESULTS, encoding="utf-8"))
|
||
# 原图相对路径映射(图片在 static/bench/orig/)
|
||
ORIG_SRC = {"asdf": "bench/orig/asdf.jpg", "qwer": "bench/orig/qwer.jpg", "girl5": "bench/orig/girl5.jpg"}
|
||
|
||
# 统计:每个模型的平均耗时、平均峰值显存
|
||
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"]):
|
||
src = img_src(r["grown_path"]) if not r["error"] else None
|
||
if src:
|
||
run_cells.append(
|
||
f'<div class="run-cell"><div class="run-label">第{i+1}次 · {r["elapsed"]:.1f}s</div>'
|
||
f'{thumb(src, f"r{i+1}", "result-img")}</div>'
|
||
)
|
||
else:
|
||
run_cells.append(
|
||
f'<div class="run-cell"><div class="run-label">第{i+1}次 · 失败</div>'
|
||
f'<div class="na">⚠ {r["error"][:30] if r["error"] else ""}</div></div>'
|
||
)
|
||
|
||
orig = ORIG_SRC.get(c["img"])
|
||
rows_html.append(f'''
|
||
<div class="combo-row">
|
||
<div class="cell-model">{MODEL_LABEL.get(m, m)}<div class="cell-sub">res={RES_LABEL.get(rlabel, rlabel)}</div></div>
|
||
<div class="cell-img">{thumb(orig, "原图", "orig-img")}<div class="run-label">{ilabel}</div></div>
|
||
<div class="cell-runs">{"".join(run_cells)}</div>
|
||
</div>''')
|
||
|
||
# 模型对比汇总
|
||
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"<tr><td>{MODEL_LABEL.get(m,m)}</td><td>{avg_t:.1f}s</td>"
|
||
f"<td>{max_p:.1f} GB</td><td>{len(s['times'])} 成功</td></tr>"
|
||
)
|
||
|
||
html = f"""<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Flux 模型矩阵测试报告 — 接口2女性花瓣形</title>
|
||
<style>
|
||
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
|
||
body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f5f5f5; color: #333; padding: 20px; }}
|
||
h1 {{ font-size: 22px; margin-bottom: 4px; }}
|
||
.subtitle {{ color: #888; font-size: 13px; margin-bottom: 16px; }}
|
||
.summary {{ background: #fff; border-radius: 10px; padding: 16px 20px; margin-bottom: 20px; box-shadow: 0 1px 4px rgba(0,0,0,.06); }}
|
||
.summary h2 {{ font-size: 16px; margin-bottom: 10px; }}
|
||
.summary table {{ border-collapse: collapse; width: 100%; font-size: 14px; }}
|
||
.summary th, .summary td {{ border: 1px solid #e5e7eb; padding: 8px 12px; text-align: left; }}
|
||
.summary th {{ background: #f9fafb; font-weight: 600; }}
|
||
.combo-row {{ display: flex; align-items: flex-start; gap: 12px; background: #fff; border-radius: 10px;
|
||
padding: 12px 16px; margin-bottom: 10px; box-shadow: 0 1px 3px rgba(0,0,0,.05); }}
|
||
.cell-model {{ min-width: 130px; font-weight: 700; font-size: 14px; padding-top: 6px; }}
|
||
.cell-sub {{ font-weight: 400; font-size: 12px; color: #6b7280; margin-top: 2px; }}
|
||
.cell-img {{ min-width: 160px; text-align: center; }}
|
||
.cell-runs {{ display: flex; gap: 10px; flex: 1; }}
|
||
.run-cell {{ text-align: center; }}
|
||
.run-label {{ font-size: 11px; color: #6b7280; margin-bottom: 4px; }}
|
||
img {{ border-radius: 6px; max-height: 200px; max-width: 100%; object-fit: contain; background: #f9fafb; }}
|
||
.orig-img {{ max-height: 180px; border: 2px solid #e5e7eb; }}
|
||
.result-img {{ max-height: 200px; }}
|
||
.na {{ color: #d1d5db; font-size: 12px; padding: 40px 20px; background: #f9fafb; border-radius: 6px; width: 150px; }}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>💇 Flux 模型矩阵测试报告</h1>
|
||
<p class="subtitle">接口2女性 · 花瓣形发型 · 4模型 × 3分辨率 × 3图 × 3次 = 108 次 · RTX 3090 24GB</p>
|
||
|
||
<div class="summary">
|
||
<h2>📊 模型对比汇总</h2>
|
||
<table>
|
||
<tr><th>模型</th><th>平均耗时</th><th>峰值显存</th><th>成功次数</th></tr>
|
||
{"".join(summary_rows)}
|
||
</table>
|
||
</div>
|
||
|
||
<h2 style="font-size:16px;margin:24px 0 12px">🖼️ 各组合对比(每行:原图 + 3次生发结果)</h2>
|
||
{"".join(rows_html)}
|
||
</body>
|
||
</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()
|