Spaces:
Running
Running
File size: 6,838 Bytes
770f650 | 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 | #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Comparative Report Generator for Cham-OCR Multi-GPU Benchmarks.
Aggregates trials from benchmark_*.json and formats the final Markdown comparison.
"""
import os
import sys
import glob
import json
import argparse
if sys.stdout and hasattr(sys.stdout, 'reconfigure'):
try:
sys.stdout.reconfigure(encoding='utf-8')
except Exception:
pass
if sys.stderr and hasattr(sys.stderr, 'reconfigure'):
try:
sys.stderr.reconfigure(encoding='utf-8')
except Exception:
pass
def generate_comparative_report(results_dir="benchmark_results", output_md="benchmark_results/COMPARATIVE_REPORT.md"):
json_files = glob.glob(os.path.join(results_dir, "benchmark_*.json"))
if not json_files:
print(f"No benchmark JSON files found in {results_dir}")
return
summaries = []
for jf in json_files:
try:
with open(jf, "r", encoding="utf-8") as f:
data = json.load(f)
summaries.append(data)
except Exception as e:
print(f"Error reading {jf}: {e}")
lines = []
lines.append("# Báo Cáo Đối Soánh Hiệu Năng & Chi Phí Huấn Luyện Cham-OCR Đa GPU")
lines.append("## (PP-OCRv4 SVTR_LCNet - Pipeline Huấn Luyện Thật)\n")
lines.append("### 1. Bảng Tổng Hợp So Sánh Các Cấu Hình Tối Ưu (Best & Max Batch)")
lines.append("| GPU | Phân Loại | Batch | Workers | IPS (mẫu/s) | GPU% | VRAM% | Reader Time | Batch Time | $/h | Est 40 Epoch | Est Cost | IPS/$ |")
lines.append("|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|")
rankings = []
for s in summaries:
gpu = s["gpu_name"]
cost = s["cost_per_hour"]
workers = s["optimal_workers"]
best_b = s["best_batch"]
max_b = s["max_safe_batch"]
trials = { (t["batch_size"], t["num_workers"]): t for t in s["all_trials"] if t.get("status") == "SUCCESS" }
# Best Trial
best_t = trials.get((best_b, workers))
if best_t:
lines.append(
f"| **{gpu}** | 🌟 **Best** | {best_b} | {workers} | "
f"**{best_t['ips']:.1f}** | {best_t['avg_gpu_util']:.0f}% | {best_t['vram_pct']:.1f}% | "
f"{best_t['avg_reader_cost']:.4f}s | {best_t['avg_batch_cost']:.4f}s | "
f"${cost:.2f} | {best_t['est_40_epoch_hours']:.1f}h | "
f"**${best_t['est_40_epoch_cost']:.2f}** | **{best_t['ips_per_dollar']:.1f}** |"
)
rankings.append({
"gpu": gpu,
"type": "Best",
"batch": best_b,
"workers": workers,
"ips": best_t["ips"],
"cost": cost,
"est_hours": best_t["est_40_epoch_hours"],
"est_cost": best_t["est_40_epoch_cost"],
"ips_per_dollar": best_t["ips_per_dollar"],
"bottleneck": s["primary_bottleneck"]
})
# Max Safe Trial (if different from Best)
if max_b != best_b:
max_t = trials.get((max_b, workers))
if max_t:
lines.append(
f"| {gpu} | 🛡️ Max Safe | {max_b} | {workers} | "
f"{max_t['ips']:.1f} | {max_t['avg_gpu_util']:.0f}% | {max_t['vram_pct']:.1f}% | "
f"{max_t['avg_reader_cost']:.4f}s | {max_t['avg_batch_cost']:.4f}s | "
f"${cost:.2f} | {max_t['est_40_epoch_hours']:.1f}h | "
f"${max_t['est_40_epoch_cost']:.2f} | {max_t['ips_per_dollar']:.1f} |"
)
lines.append("\n### 2. Kết Luận Chi Tiết Cho Từng Machine")
for s in summaries:
gpu = s["gpu_name"]
cost = s["cost_per_hour"]
workers = s["optimal_workers"]
best_b = s["best_batch"]
max_b = s["max_safe_batch"]
bottleneck = s["primary_bottleneck"]
best_t = s.get("best_trial")
lines.append(f"\n#### 🔹 Machine: **{gpu}** (${cost:.2f}/h)")
lines.append(f"- **Max Batch Chạy Được**: `{max_b}` (ngưỡng giới hạn phần cứng trước khi OOM).")
lines.append(f"- **Best Batch Nên Dùng**: `{best_b}` (điểm ngọt cho throughput cao nhất trước khi bão hòa).")
lines.append(f"- **Num Workers Tối Ưu**: `{workers}` worker.")
lines.append(f"- **Điểm Nghẽn Chính (Bottleneck)**: `{bottleneck}`.")
if best_t:
lines.append(f"- **Thời Gian Huấn Luyện Full 40 Epoch (10,000,000 mẫu)**: `{best_t['est_40_epoch_hours']:.2f} giờ` (~`{best_t['est_40_epoch_hours']*60:.0f} phút`).")
lines.append(f"- **Chi Phí Ước Tính Full 40 Epoch**: `${best_t['est_40_epoch_cost']:.2f}`.")
lines.append(f"- **Hiệu Năng / Chi Phí (P/P)**: `{best_t['ips_per_dollar']:.1f} mẫu / $`.")
# Sort rankings by IPS/$ descending
rankings.sort(key=lambda x: x["ips_per_dollar"], reverse=True)
lines.append("\n### 3. Xếp Hạng Hiệu Quả Đầu Tư (Performance / Price Ranking)")
lines.append("| Hạng | GPU | Best Batch | Tốc Độ (IPS) | Thời Gian Full 40 Epoch | Tổng Chi Phí ($) | Hiệu Suất Kinh Tế (IPS/$) |")
lines.append("|:---:|:---|:---:|:---:|:---:|:---:|:---:|")
for i, r in enumerate(rankings, 1):
medal = "🥇" if i == 1 else ("🥈" if i == 2 else ("🥉" if i == 3 else f"#{i}"))
lines.append(f"| {medal} | **{r['gpu']}** | {r['batch']} | {r['ips']:.1f} mẫu/s | {r['est_hours']:.1f}h | **${r['est_cost']:.2f}** | **{r['ips_per_dollar']:.1f}** |")
if rankings:
top = rankings[0]
lines.append(f"\n> 🏆 **LỰA CHỌN TỐI ƯU NHẤT (P/P)**: **{top['gpu']}** là cỗ máy kinh tế nhất với **{top['ips_per_dollar']:.1f} mẫu / $**, tổng chi phí train full 40 epoch chỉ **${top['est_cost']:.2f}** trong **{top['est_hours']:.1f} giờ**.")
report_content = "\n".join(lines)
os.makedirs(os.path.dirname(os.path.abspath(output_md)), exist_ok=True)
with open(output_md, "w", encoding="utf-8") as f:
f.write(report_content)
print(f"\n✅ Comparative report written to: {output_md}")
print("\n" + report_content)
return report_content
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate Comparative Benchmark Report")
parser.add_argument("-d", "--results_dir", type=str, default="ocr-training/benchmark_results", help="Directory with benchmark JSONs")
parser.add_argument("-o", "--output_md", type=str, default="ocr-training/benchmark_results/COMPARATIVE_REPORT.md", help="Output Markdown report path")
args = parser.parse_args()
generate_comparative_report(args.results_dir, args.output_md)
|