- 测试结果图(258张)和原图(3张)以JPG存入 static/bench/{matrix,hairstyle,orig}/
- 报告HTML改为 <img src> 引用相对路径,体积从 ~45MB 降至 ~30KB
- 报告和图片均部署在 static/ 下,可通过URL直接访问
129 lines
5.0 KiB
Python
129 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""把发型对比测试结果生成 HTML 报告。
|
||
15行(3图×5发型) × 10列(4b@896 + 9b三模型×三分辨率),每行首列=原图。
|
||
图片 base64 内嵌,自包含单文件。
|
||
"""
|
||
import base64
|
||
import json
|
||
import os
|
||
from pathlib import Path
|
||
|
||
OUT = Path("/home/ubuntu/hair/benchmark_out/hairstyle")
|
||
RESULTS = OUT / "results.json"
|
||
HTML = OUT / "report.html"
|
||
|
||
|
||
def img_src(path):
|
||
"""把绝对路径转成报告里的相对 URL(报告在 static/,图片在 static/bench/)。"""
|
||
if not path:
|
||
return None
|
||
p = str(path)
|
||
if "benchmark_out/hairstyle/" in p:
|
||
return "bench/hairstyle/" + os.path.basename(p)
|
||
if "benchmark_out/matrix/" in p:
|
||
return "bench/matrix/" + os.path.basename(p)
|
||
return None
|
||
|
||
|
||
def main():
|
||
d = json.load(open(RESULTS, encoding="utf-8"))
|
||
columns = d["columns"]
|
||
rows = d["rows"]
|
||
|
||
# 统计每列的平均耗时、峰值显存
|
||
col_stats = {}
|
||
for ct in columns:
|
||
times, peaks = [], []
|
||
for r in rows:
|
||
for c in r["cells"]:
|
||
if c.get("title") == ct and not c.get("error"):
|
||
times.append(c["elapsed"])
|
||
peaks.append(c["gpu_peak"])
|
||
col_stats[ct] = {
|
||
"avg_t": sum(times) / len(times) if times else 0,
|
||
"max_p": max(peaks) / 1024 if peaks else 0,
|
||
}
|
||
|
||
# 表头:原图 + 10列
|
||
headers = ['<th class="col-label">原图</th>']
|
||
for ct in columns:
|
||
s = col_stats[ct]
|
||
headers.append(
|
||
f'<th class="col-label"><div class="col-title">{ct}</div>'
|
||
f'<div class="col-stat">{s["avg_t"]:.0f}s · {s["max_p"]:.0f}G</div></th>'
|
||
)
|
||
|
||
# 表体:15行
|
||
body_rows = []
|
||
for r in rows:
|
||
# 发型+图标签
|
||
label = f'<div class="row-label">{r["img"]}<br><b>{r["hair_name"]}</b></div>'
|
||
# 原图
|
||
ORIG_SRC = {"asdf": "bench/orig/asdf.jpg", "qwer": "bench/orig/qwer.jpg", "girl5": "bench/orig/girl5.jpg"}
|
||
orig = ORIG_SRC.get(r["img"])
|
||
cells = [f'<td class="cell-orig"><div class="row-label-cell">{label}</div>'
|
||
f'<img class="orig-img" src="{orig}"></td>']
|
||
# 10个结果列
|
||
for ct in columns:
|
||
c = next((x for x in r["cells"] if x.get("title") == ct), {})
|
||
src = img_src(c.get("grown_path")) if not c.get("error") else None
|
||
if src:
|
||
cells.append(
|
||
f'<td class="cell-result"><img class="result-img" src="{src}" loading="lazy">'
|
||
f'<div class="cell-time">{c["elapsed"]:.1f}s</div></td>')
|
||
else:
|
||
cells.append(f'<td class="cell-result"><div class="na">⚠</div></td>')
|
||
body_rows.append(f'<tr>{"".join(cells)}</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>发型对比测试报告 — 4模型×3分辨率</title>
|
||
<style>
|
||
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
|
||
body {{ font-family: -apple-system, "Segoe UI", sans-serif; background: #f5f5f5; padding: 16px; }}
|
||
h1 {{ font-size: 20px; margin-bottom: 4px; }}
|
||
.subtitle {{ color: #888; font-size: 12px; margin-bottom: 12px; }}
|
||
.legend {{ background: #fff; border-radius: 8px; padding: 10px 16px; margin-bottom: 12px; font-size: 12px; color: #555; }}
|
||
.scroll-wrap {{ overflow-x: auto; }}
|
||
table {{ border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden;
|
||
box-shadow: 0 1px 4px rgba(0,0,0,.06); }}
|
||
th, td {{ border: 1px solid #eee; padding: 6px; vertical-align: top; text-align: center; }}
|
||
th {{ background: #f9fafb; position: sticky; top: 0; }}
|
||
.col-label {{ min-width: 110px; max-width: 130px; }}
|
||
.col-title {{ font-size: 12px; font-weight: 700; color: #374151; }}
|
||
.col-stat {{ font-size: 10px; color: #9ca3af; margin-top: 2px; }}
|
||
.row-label {{ font-size: 11px; color: #6b7280; }}
|
||
.row-label b {{ color: #1f2937; }}
|
||
.row-label-cell {{ font-size: 11px; color: #6b7280; margin-bottom: 4px; }}
|
||
.row-label-cell b {{ color: #1f2937; font-size: 13px; }}
|
||
img {{ border-radius: 4px; max-width: 120px; max-height: 150px; object-fit: contain; background: #f3f4f6; }}
|
||
.orig-img {{ border: 2px solid #d1d5db; max-height: 130px; }}
|
||
.cell-time {{ font-size: 10px; color: #9ca3af; margin-top: 2px; }}
|
||
.na {{ color: #d1d5db; font-size: 16px; padding: 40px; }}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>💇 发型对比测试报告</h1>
|
||
<p class="subtitle">接口2女性 · 3图×5发型=15行 · 每行: 4B@896(1) + 9B(fp8/Q5/Q4)×(原图/896/640)(9) · 150/150成功 · RTX3090</p>
|
||
<div class="legend">列标题下显示<b>平均耗时 · 峰值显存</b>。横向滚动查看更多列。原图列含图片名+发型名。</div>
|
||
<div class="scroll-wrap">
|
||
<table>
|
||
<tr>{"".join(headers)}</tr>
|
||
{"".join(body_rows)}
|
||
</table>
|
||
</div>
|
||
</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()
|